Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e608e85d56 | |||
| 58104a9a4d | |||
| 8d68dcabb5 | |||
| a5e5389d6c | |||
| 8de862c82f | |||
| 5141303ee1 | |||
| 3d95680cbc | |||
| 78663b873c | |||
| 0a106026dd | |||
| 999ca6274c | |||
| 36b9177069 | |||
| 8cf7bf859b | |||
| 2e54b62f60 | |||
| df46069d92 | |||
| e31014dde4 | |||
| f9a14581e1 | |||
| 95ec005894 | |||
| 73d271b8bc | |||
| 0c3b56e44a | |||
| 736f340979 | |||
| 49e62d35c3 | |||
| 810bce7495 | |||
| aff876aa05 | |||
| 9511644ce6 | |||
| 21d73e5f69 | |||
| d62f3fb936 | |||
| 0a011b6075 | |||
| 4e561dc764 | |||
| a55256ad82 | |||
| ab26ef64a5 | |||
| 6d1610eee0 | |||
| 2ba143d6ea | |||
| bd542ac308 | |||
| e71ffd1a77 | |||
| 1ac968f63c | |||
| dc4f62a085 | |||
| ad91d4f1ce | |||
| 8d4c7512ab | |||
| 12d5837526 | |||
| c82d2abab4 | |||
| 26cb717a08 | |||
| 0b1cc0ef5b | |||
| 05dc9138b4 | |||
| a7dcacb26c | |||
| 3ac3f871e4 | |||
| b180c0bbe6 | |||
| e4987f3bde | |||
| ee2690c2cc | |||
| 4cad26f793 | |||
| 84f3d5fec5 | |||
| bb3939d570 | |||
| b303f708f5 | |||
| d39550e090 | |||
| 22d956f38a | |||
| dab7728138 | |||
| 4038b6bc51 | |||
| bcaf3a246c | |||
| 4fb115fcbc | |||
| bfd4b2b6de | |||
| c0cd3fc5c2 | |||
| f95c4393d2 | |||
| e2eb5fabcc | |||
| 7275e8ff0d | |||
| accfc3df12 | |||
| 35392483d9 | |||
| 9770851fd4 | |||
| e013a6f087 | |||
| 1ec9ff20b1 | |||
| 9b0dea80c9 | |||
| eea1ea7ed0 | |||
| 85cd46bfc7 | |||
| a7ca394864 | |||
| e178a0795a | |||
| e8b0470ceb | |||
| c08abfdfdf | |||
| b1c765eb51 | |||
| 4b0f7d45e8 | |||
| 9e6271b1dc | |||
| a96eb31dc9 | |||
| 221a7809b6 | |||
| 8c33243c90 | |||
| c4b07b98aa | |||
| 1fda80a86b |
@@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
'root': true,
|
||||
'env': {
|
||||
'node': true
|
||||
},
|
||||
'extends': [
|
||||
'eslint:recommended',
|
||||
'plugin:vue/vue3-essential'
|
||||
],
|
||||
'rules': {
|
||||
'vue/no-use-v-if-with-v-for': 'off'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
name: Docker Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
${{ secrets.DOCKER_REPO }}/mayswind/ezbookkeeping
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=raw,value=latest
|
||||
|
||||
- name: Set up the environment
|
||||
run: |
|
||||
cat >> docker/custom-backend-pre-setup.sh <<EOF
|
||||
#!/bin/sh
|
||||
${{ secrets.CUSTOM_BACKEND_PRE_SETUP }}
|
||||
EOF
|
||||
cat >> docker/custom-frontend-pre-setup.sh <<EOF
|
||||
#!/bin/sh
|
||||
${{ secrets.CUSTOM_FRONTEND_PRE_SETUP }}
|
||||
EOF
|
||||
chmod +x docker/custom-backend-pre-setup.sh
|
||||
chmod +x docker/custom-frontend-pre-setup.sh
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
env:
|
||||
ACTIONS_RUNTIME_TOKEN: '' # See https://gitea.com/gitea/act_runner/issues/119
|
||||
with:
|
||||
file: Dockerfile
|
||||
context: .
|
||||
push: true
|
||||
build-args: |
|
||||
RELEASE_BUILD=1
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -0,0 +1,49 @@
|
||||
name: Docker Snapshot
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
${{ secrets.DOCKER_REPO }}/mayswind/ezbookkeeping
|
||||
tags: |
|
||||
type=raw,value=SNAPSHOT-{{date 'YYYYMMDD'}}
|
||||
type=raw,value=latest-snapshot
|
||||
type=sha,format=short,prefix=SNAPSHOT-
|
||||
|
||||
- name: Set up the environment
|
||||
run: |
|
||||
sed -i 's#FROM #FROM ${{ secrets.DOCKER_REPO }}/mirrors/#g' Dockerfile
|
||||
cat >> docker/custom-backend-pre-setup.sh <<EOF
|
||||
#!/bin/sh
|
||||
${{ secrets.CUSTOM_BACKEND_PRE_SETUP }}
|
||||
EOF
|
||||
cat >> docker/custom-frontend-pre-setup.sh <<EOF
|
||||
#!/bin/sh
|
||||
${{ secrets.CUSTOM_FRONTEND_PRE_SETUP }}
|
||||
EOF
|
||||
chmod +x docker/custom-backend-pre-setup.sh
|
||||
chmod +x docker/custom-frontend-pre-setup.sh
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
env:
|
||||
ACTIONS_RUNTIME_TOKEN: '' # See https://gitea.com/gitea/act_runner/issues/119
|
||||
with:
|
||||
file: Dockerfile
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -9,35 +9,44 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Set up docker tag
|
||||
id: vars
|
||||
run: echo ::set-output name=RELEASE_TAG::${GITHUB_REF/refs\/tags\/v/}
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
-
|
||||
name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
${{ secrets.DOCKER_USERNAME }}/ezbookkeeping
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=raw,value=latest
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
file: Dockerfile
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64/v8,linux/arm/v7,linux/arm/v6
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm64/v8
|
||||
linux/arm/v7
|
||||
linux/arm/v6
|
||||
push: true
|
||||
build-args: |
|
||||
RELEASE_BUILD=1
|
||||
tags: |
|
||||
${{ secrets.DOCKER_USERNAME }}/ezbookkeeping:${{ steps.vars.outputs.RELEASE_TAG }}
|
||||
${{ secrets.DOCKER_USERNAME }}/ezbookkeeping:latest
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -9,33 +9,41 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Set up docker tag
|
||||
id: vars
|
||||
run: echo ::set-output name=BUILD_DATE::$(date '+%Y%m%d')
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
-
|
||||
name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
${{ secrets.DOCKER_USERNAME }}/ezbookkeeping
|
||||
tags: |
|
||||
type=raw,value=SNAPSHOT-{{date 'YYYYMMDD'}}
|
||||
type=raw,value=latest-snapshot
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
file: Dockerfile
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64/v8,linux/arm/v7,linux/arm/v6
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm64/v8
|
||||
linux/arm/v7
|
||||
linux/arm/v6
|
||||
push: true
|
||||
tags: |
|
||||
${{ secrets.DOCKER_USERNAME }}/ezbookkeeping:SNAPSHOT-${{ steps.vars.outputs.BUILD_DATE }}
|
||||
${{ secrets.DOCKER_USERNAME }}/ezbookkeeping:latest-snapshot
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -0,0 +1,28 @@
|
||||
name: Docker Snapshot
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
-
|
||||
name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
-
|
||||
name: Build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
file: Dockerfile
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: false
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
# Build backend binary file
|
||||
FROM golang:1.20.2-alpine3.17 AS be-builder
|
||||
FROM golang:1.20.4-alpine3.17 AS be-builder
|
||||
ARG RELEASE_BUILD
|
||||
ENV RELEASE_BUILD=$RELEASE_BUILD
|
||||
WORKDIR /go/src/github.com/mayswind/ezbookkeeping
|
||||
@@ -9,7 +9,7 @@ RUN apk add git gcc g++ libc-dev
|
||||
RUN ./build.sh backend
|
||||
|
||||
# Build frontend files
|
||||
FROM node:18.15.0-alpine3.17 AS fe-builder
|
||||
FROM node:18.16.0-alpine3.17 AS fe-builder
|
||||
ARG RELEASE_BUILD
|
||||
ENV RELEASE_BUILD=$RELEASE_BUILD
|
||||
WORKDIR /go/src/github.com/mayswind/ezbookkeeping
|
||||
@@ -19,7 +19,7 @@ RUN apk add git
|
||||
RUN ./build.sh frontend
|
||||
|
||||
# Package docker image
|
||||
FROM alpine:3.17.2
|
||||
FROM alpine:3.17.3
|
||||
LABEL maintainer="MaysWind <i@mayswind.net>"
|
||||
RUN addgroup -S -g 1000 ezbookkeeping && adduser -S -G ezbookkeeping -u 1000 ezbookkeeping
|
||||
RUN apk --no-cache add tzdata
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
TYPE=""
|
||||
NO_LINT="0"
|
||||
NO_TEST="0"
|
||||
RELEASE=${RELEASE_BUILD:-"0"}
|
||||
RELEASE_TYPE="unknown"
|
||||
VERSION=""
|
||||
@@ -40,6 +42,8 @@ Options:
|
||||
-r, --release Build release (The script will use environment variable "RELEASE_BUILD" to detect whether this is release building by default)
|
||||
-o, --output <filename> Package file name (For "package" type only)
|
||||
-t, --tag Docker tag (For "docker" type only)
|
||||
--no-lint Do not execute lint check before building
|
||||
--no-test Do not execute unit testing before building
|
||||
-h, --help Show help
|
||||
EOF
|
||||
}
|
||||
@@ -63,6 +67,12 @@ parse_args() {
|
||||
DOCKER_TAG="$2"
|
||||
shift
|
||||
;;
|
||||
--no-lint)
|
||||
NO_LINT="1"
|
||||
;;
|
||||
--no-test)
|
||||
NO_TEST="1"
|
||||
;;
|
||||
--help | -h)
|
||||
show_help
|
||||
exit 0
|
||||
@@ -111,6 +121,27 @@ set_build_parameters() {
|
||||
}
|
||||
|
||||
build_backend() {
|
||||
if [ "$NO_LINT" = "0" ]; then
|
||||
echo "Executing backend lint checking..."
|
||||
go vet -v ./...
|
||||
|
||||
if [ "$?" != "0" ]; then
|
||||
echo_red "Error: Failed to pass lint checking"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$NO_TEST" = "0" ]; then
|
||||
echo "Executing backend unit testing..."
|
||||
go clean -cache
|
||||
go test ./... -v
|
||||
|
||||
if [ "$?" != "0" ]; then
|
||||
echo_red "Error: Failed to pass unit testing"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
backend_build_extra_arguments="-X main.Version=$VERSION"
|
||||
backend_build_extra_arguments="$backend_build_extra_arguments -X main.CommitHash=$COMMIT_HASH"
|
||||
|
||||
@@ -125,18 +156,26 @@ build_backend() {
|
||||
}
|
||||
|
||||
build_frontend() {
|
||||
frontend_build_arguments="";
|
||||
|
||||
if [ "$RELEASE" = "0" ]; then
|
||||
frontend_build_arguments="--buildUnixTime=$BUILD_UNIXTIME"
|
||||
fi
|
||||
|
||||
echo "Pulling frontend dependencies..."
|
||||
npm install
|
||||
|
||||
if [ "$NO_LINT" = "0" ]; then
|
||||
echo "Executing frontend lint checking..."
|
||||
npm run lint
|
||||
|
||||
if [ "$?" != "0" ]; then
|
||||
echo_red "Error: Failed to pass lint checking"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Building frontend files ($RELEASE_TYPE)..."
|
||||
export NODE_OPTIONS=--openssl-legacy-provider
|
||||
npm run build -- "$frontend_build_arguments"
|
||||
|
||||
if [ "$RELEASE" = "0" ]; then
|
||||
buildUnixTime=$BUILD_UNIXTIME npm run build
|
||||
else
|
||||
npm run build
|
||||
fi
|
||||
}
|
||||
|
||||
build_package() {
|
||||
|
||||
+8
-3
@@ -398,10 +398,15 @@ func printUserInfo(user *models.User) {
|
||||
fmt.Printf("[Nickname] %s\n", user.Nickname)
|
||||
fmt.Printf("[Password] %s\n", user.Password)
|
||||
fmt.Printf("[Salt] %s\n", user.Salt)
|
||||
fmt.Printf("[DefaultCurrency] %s\n", user.DefaultCurrency)
|
||||
fmt.Printf("[DefaultAccountId] %d\n", user.DefaultAccountId)
|
||||
fmt.Printf("[FirstDayOfWeek] %s\n", user.FirstDayOfWeek)
|
||||
fmt.Printf("[TransactionEditScope] %s\n", user.TransactionEditScope)
|
||||
fmt.Printf("[TransactionEditScope] %s (%d)\n", user.TransactionEditScope, user.TransactionEditScope)
|
||||
fmt.Printf("[Language] %s\n", user.Language)
|
||||
fmt.Printf("[DefaultCurrency] %s\n", user.DefaultCurrency)
|
||||
fmt.Printf("[FirstDayOfWeek] %s (%d)\n", user.FirstDayOfWeek, user.FirstDayOfWeek)
|
||||
fmt.Printf("[LongDateFormat] %s (%d)\n", user.LongDateFormat, user.LongDateFormat)
|
||||
fmt.Printf("[ShortDateFormat] %s (%d)\n", user.ShortDateFormat, user.ShortDateFormat)
|
||||
fmt.Printf("[LongTimeFormat] %s (%d)\n", user.LongTimeFormat, user.LongTimeFormat)
|
||||
fmt.Printf("[ShortTimeFormat] %s (%d)\n", user.ShortTimeFormat, user.ShortTimeFormat)
|
||||
fmt.Printf("[Deleted] %t\n", user.Deleted)
|
||||
fmt.Printf("[EmailVerified] %t\n", user.EmailVerified)
|
||||
fmt.Printf("[CreatedAt] %s (%d)\n", utils.FormatUnixTimeToLongDateTimeInServerTimezone(user.CreatedUnixTime), user.CreatedUnixTime)
|
||||
|
||||
+33
-1
@@ -110,13 +110,17 @@ func startWebServer(c *cli.Context) error {
|
||||
router.Static("/mobile/css", filepath.Join(config.StaticRootPath, "css"))
|
||||
router.Static("/mobile/img", filepath.Join(config.StaticRootPath, "img"))
|
||||
router.Static("/mobile/fonts", filepath.Join(config.StaticRootPath, "fonts"))
|
||||
router.Static("/mobile/sw", filepath.Join(config.StaticRootPath, "sw"))
|
||||
router.StaticFile("/mobile/favicon.ico", filepath.Join(config.StaticRootPath, "favicon.ico"))
|
||||
router.StaticFile("/mobile/favicon.png", filepath.Join(config.StaticRootPath, "favicon.png"))
|
||||
router.StaticFile("/mobile/touchicon.png", filepath.Join(config.StaticRootPath, "touchicon.png"))
|
||||
router.StaticFile("/mobile/manifest.json", filepath.Join(config.StaticRootPath, "manifest.json"))
|
||||
router.StaticFile("/mobile/sw.js", filepath.Join(config.StaticRootPath, "sw.js"))
|
||||
|
||||
workboxFileNames := utils.ListFileNamesWithPrefixAndSuffix(config.StaticRootPath, "workbox-", ".js")
|
||||
for i := 0; i < len(workboxFileNames); i++ {
|
||||
router.StaticFile("/mobile/"+workboxFileNames[i], filepath.Join(config.StaticRootPath, workboxFileNames[i]))
|
||||
}
|
||||
|
||||
desktopEntryRoute := router.Group("/desktop")
|
||||
desktopEntryRoute.Use(bindMiddleware(middlewares.ServerSettingsCookie(config)))
|
||||
{
|
||||
@@ -133,6 +137,21 @@ func startWebServer(c *cli.Context) error {
|
||||
|
||||
router.GET("/healthz.json", bindApi(api.Healths.HealthStatusHandler))
|
||||
|
||||
if config.Mode == settings.MODE_DEVELOPMENT {
|
||||
devRoute := router.Group("/dev")
|
||||
devRoute.GET("/cookies", bindMiddleware(middlewares.ServerSettingsCookie(config)))
|
||||
}
|
||||
|
||||
proxyRoute := router.Group("/proxy")
|
||||
proxyRoute.Use(bindMiddleware(middlewares.JWTAuthorizationByQueryString))
|
||||
{
|
||||
if config.EnableMapDataFetchProxy {
|
||||
if config.MapProvider == settings.OpenStreetMapProvider {
|
||||
proxyRoute.GET("/openstreetmap/tile/:zoomLevel/:coordinateX/:fileName", bindProxy(api.MapImages.OpenStreetMapTileImageProxyHandler))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apiRoute := router.Group("/api")
|
||||
|
||||
apiRoute.Use(bindMiddleware(middlewares.RequestId(config)))
|
||||
@@ -284,3 +303,16 @@ func bindCsv(fn core.DataHandlerFunc) gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func bindProxy(fn core.ProxyHandlerFunc) gin.HandlerFunc {
|
||||
return func(ginCtx *gin.Context) {
|
||||
c := core.WrapContext(ginCtx)
|
||||
proxy, err := fn(c)
|
||||
|
||||
if err != nil {
|
||||
utils.PrintDataErrorResult(c, "text/text", err)
|
||||
} else {
|
||||
proxy.ServeHTTP(c.Writer, c.Request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-1
@@ -110,8 +110,21 @@ enable_register = true
|
||||
# Set to true to allow users to export their data
|
||||
enable_export = true
|
||||
|
||||
[map]
|
||||
# Map provider, supports `openstreetmap`
|
||||
map_provider = openstreetmap
|
||||
|
||||
# Set to true to use the ezbookkeeping server to proxy map data requests, for "openstreetmap"
|
||||
map_data_fetch_proxy = false
|
||||
|
||||
[exchange_rates]
|
||||
# Exchange rates data source, supports "euro_central_bank", "bank_of_canada", "reserve_bank_of_australia", "czech_national_bank", "national_bank_of_poland" currently
|
||||
# Exchange rates data source, supports the following types:
|
||||
# "euro_central_bank"
|
||||
# "bank_of_canada"
|
||||
# "reserve_bank_of_australia",
|
||||
# "czech_national_bank"
|
||||
# "national_bank_of_poland"
|
||||
# "monetary_authority_of_singapore"
|
||||
data_source = euro_central_bank
|
||||
|
||||
# Requesting exchange rates data timeout (0 - 4294967295 milliseconds), default is 10000 (10 seconds)
|
||||
|
||||
@@ -5,16 +5,16 @@ go 1.20
|
||||
require (
|
||||
github.com/gin-contrib/gzip v0.0.6
|
||||
github.com/gin-gonic/gin v1.9.0
|
||||
github.com/go-playground/validator/v10 v10.12.0
|
||||
github.com/go-sql-driver/mysql v1.7.0
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/lib/pq v1.10.7
|
||||
github.com/go-playground/validator/v10 v10.14.0
|
||||
github.com/go-sql-driver/mysql v1.7.1
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/mattn/go-sqlite3 v1.14.16
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/urfave/cli/v2 v2.25.0
|
||||
golang.org/x/crypto v0.7.0
|
||||
github.com/urfave/cli/v2 v2.25.4
|
||||
golang.org/x/crypto v0.9.0
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
xorm.io/xorm v1.3.2
|
||||
)
|
||||
@@ -25,6 +25,7 @@ require (
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
@@ -32,7 +33,7 @@ require (
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
|
||||
github.com/leodido/go-urn v1.2.2 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
@@ -44,9 +45,9 @@ require (
|
||||
github.com/ugorji/go/codec v1.2.9 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
xorm.io/builder v0.3.12 // indirect
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
||||
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
||||
gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
@@ -73,6 +72,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
|
||||
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
||||
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
|
||||
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
|
||||
@@ -88,7 +89,6 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
@@ -96,12 +96,12 @@ github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||
github.com/go-playground/validator/v10 v10.12.0 h1:E4gtWgxWxp8YSxExrQFv5BpCahla0PVF2oTTEYaWQGI=
|
||||
github.com/go-playground/validator/v10 v10.12.0/go.mod h1:hCAPuzYvKdP33pxWa+2+6AIKXEKqjIUyqsNCtbsSJrA=
|
||||
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
||||
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
@@ -113,8 +113,8 @@ github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFG
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@@ -134,12 +134,10 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
@@ -169,7 +167,6 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
@@ -230,7 +227,6 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
@@ -241,23 +237,21 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxv
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4=
|
||||
github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
|
||||
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
||||
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
@@ -307,9 +301,7 @@ github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtb
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
|
||||
@@ -355,12 +347,10 @@ github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||
@@ -368,7 +358,6 @@ github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThC
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
@@ -418,8 +407,8 @@ github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU
|
||||
github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli/v2 v2.25.0 h1:ykdZKuQey2zq0yin/l7JOm9Mh+pg72ngYMeB0ABn6q8=
|
||||
github.com/urfave/cli/v2 v2.25.0/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
|
||||
github.com/urfave/cli/v2 v2.25.4 h1:HyYwPrTO3im9rYhUff/ZNs78eolxt0nJ4LN+9yJKSH4=
|
||||
github.com/urfave/cli/v2 v2.25.4/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
@@ -460,8 +449,8 @@ golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWP
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
@@ -471,7 +460,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -490,8 +478,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -532,8 +520,8 @@ golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -541,8 +529,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -562,13 +550,11 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
@@ -595,24 +581,20 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
@@ -622,7 +604,6 @@ honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU=
|
||||
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.33.9/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
@@ -637,7 +618,6 @@ modernc.org/cc/v3 v3.35.10/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g
|
||||
modernc.org/cc/v3 v3.35.15/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.16/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.17/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/cc/v3 v3.35.18 h1:rMZhRcWrba0y3nVmdiQ7kxAgOOSq2m2f2VzjHLgEs6U=
|
||||
modernc.org/cc/v3 v3.35.18/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
|
||||
modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60=
|
||||
modernc.org/ccgo/v3 v3.10.0/go.mod h1:c0yBmkRFi7uW4J7fwx/JiijwOjeAeR2NoSaRVFPmjMw=
|
||||
@@ -673,7 +653,6 @@ modernc.org/ccgo/v3 v3.12.66/go.mod h1:jUuxlCFZTUZLMV08s7B1ekHX5+LIAurKTTaugUr/E
|
||||
modernc.org/ccgo/v3 v3.12.67/go.mod h1:Bll3KwKvGROizP2Xj17GEGOTrlvB1XcVaBrC90ORO84=
|
||||
modernc.org/ccgo/v3 v3.12.73/go.mod h1:hngkB+nUUqzOf3iqsM48Gf1FZhY599qzVg1iX+BT3cQ=
|
||||
modernc.org/ccgo/v3 v3.12.81/go.mod h1:p2A1duHoBBg1mFtYvnhAnQyI6vL0uw5PGYLSIgF6rYY=
|
||||
modernc.org/ccgo/v3 v3.12.82 h1:wudcnJyjLj1aQQCXF3IM9Gz2X6UNjw+afIghzdtn0v8=
|
||||
modernc.org/ccgo/v3 v3.12.82/go.mod h1:ApbflUfa5BKadjHynCficldU1ghjen84tuM5jRynB7w=
|
||||
modernc.org/ccorpus v1.11.1/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||
@@ -711,24 +690,17 @@ modernc.org/libc v1.11.71/go.mod h1:DUOmMYe+IvKi9n6Mycyx3DbjfzSKrdr/0Vgt3j7P5gw=
|
||||
modernc.org/libc v1.11.75/go.mod h1:dGRVugT6edz361wmD9gk6ax1AbDSe0x5vji0dGJiPT0=
|
||||
modernc.org/libc v1.11.82/go.mod h1:NF+Ek1BOl2jeC7lw3a7Jj5PWyHPwWD4aq3wVKxqV1fI=
|
||||
modernc.org/libc v1.11.86/go.mod h1:ePuYgoQLmvxdNT06RpGnaDKJmDNEkV7ZPKI2jnsvZoE=
|
||||
modernc.org/libc v1.11.87 h1:PzIzOqtlzMDDcCzJ5cUP6h/Ku6Fa9iyflP2ccTY64aE=
|
||||
modernc.org/libc v1.11.87/go.mod h1:Qvd5iXTeLhI5PS0XSyqMY99282y+3euapQFxM7jYnpY=
|
||||
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
|
||||
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
|
||||
modernc.org/memory v1.0.5 h1:XRch8trV7GgvTec2i7jc33YlUI0RKVDBvZ5eZ5m8y14=
|
||||
modernc.org/memory v1.0.5/go.mod h1:B7OYswTRnfGg+4tDH1t1OeUNnsy2viGTdME4tzd+IjM=
|
||||
modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
|
||||
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.14.2 h1:ohsW2+e+Qe2To1W6GNezzKGwjXwSax6R+CrhRxVaFbE=
|
||||
modernc.org/sqlite v1.14.2/go.mod h1:yqfn85u8wVOE6ub5UT8VI9JjhrwBUUCNyTACN0h6Sx8=
|
||||
modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs=
|
||||
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
|
||||
modernc.org/tcl v1.8.13/go.mod h1:V+q/Ef0IJaNUSECieLU4o+8IScapxnMyFV6i/7uQlAY=
|
||||
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
|
||||
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
||||
Generated
+3401
-12290
File diff suppressed because it is too large
Load Diff
+29
-41
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ezbookkeeping",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -12,56 +12,44 @@
|
||||
"url": "https://github.com/mayswind/ezbookkeeping/issues"
|
||||
},
|
||||
"scripts": {
|
||||
"serve": "set NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve",
|
||||
"build": "set NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
"serve": "cross-env NODE_ENV=development vite",
|
||||
"build": "cross-env NODE_ENV=production vite build",
|
||||
"serve:dist": "vite preview",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.3.4",
|
||||
"@vuepic/vue-datepicker": "^5.1.2",
|
||||
"axios": "^1.4.0",
|
||||
"cbor-js": "^0.1.0",
|
||||
"core-js": "^3.29.1",
|
||||
"clipboard": "^2.0.11",
|
||||
"crypto-js": "^4.1.1",
|
||||
"framework7": "^5.7.14",
|
||||
"dom7": "^4.0.6",
|
||||
"framework7": "^8.0.5",
|
||||
"framework7-icons": "^5.0.5",
|
||||
"framework7-vue": "^5.7.14",
|
||||
"js-cookie": "^3.0.1",
|
||||
"framework7-vue": "^8.0.5",
|
||||
"js-cookie": "^3.0.5",
|
||||
"leaflet": "^1.9.4",
|
||||
"line-awesome": "^1.3.0",
|
||||
"moment": "^2.29.4",
|
||||
"moment-timezone": "^0.5.41",
|
||||
"moment-timezone": "^0.5.43",
|
||||
"register-service-worker": "^1.7.2",
|
||||
"ua-parser-js": "^1.0.34",
|
||||
"vue": "^2.7.14",
|
||||
"vue-clipboard2": "^0.3.3",
|
||||
"vue-i18n": "^8.28.2",
|
||||
"vue-pincode-input": "^0.4.0",
|
||||
"vuex": "^3.6.2"
|
||||
"skeleton-elements": "^4.0.1",
|
||||
"swiper": "^9.3.2",
|
||||
"ua-parser-js": "^1.0.35",
|
||||
"vue": "^3.3.4",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vuex": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^4.5.19",
|
||||
"@vue/cli-plugin-eslint": "^4.5.19",
|
||||
"@vue/cli-plugin-pwa": "^4.5.19",
|
||||
"@vue/cli-service": "^4.5.19",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-plugin-component": "^1.1.1",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"git-revision-webpack-plugin": "^3.0.6",
|
||||
"moment-locales-webpack-plugin": "^1.2.0",
|
||||
"vue-template-compiler": "^2.7.14"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
},
|
||||
"rules": {}
|
||||
"@vitejs/plugin-vue": "^4.2.0",
|
||||
"@vue/compiler-sfc": "^3.3.4",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.41.0",
|
||||
"eslint-plugin-vue": "^9.14.1",
|
||||
"git-rev-sync": "^3.0.2",
|
||||
"postcss-preset-env": "^8.4.1",
|
||||
"vite": "^4.3.5",
|
||||
"vite-plugin-pwa": "^0.15.1"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
)
|
||||
|
||||
const openStreetMapTileImageUrlFormat = "https://tile.openstreetmap.org/%s/%s/%s" // https://tile.openstreetmap.org/{z}/{x}/{y}.png
|
||||
|
||||
// MapImageProxy represents map image proxy
|
||||
type MapImageProxy struct {
|
||||
}
|
||||
|
||||
// Initialize a map image proxy singleton instance
|
||||
var (
|
||||
MapImages = &MapImageProxy{}
|
||||
)
|
||||
|
||||
// OpenStreetMapTileImageProxyHandler returns open street map tile image
|
||||
func (p *MapImageProxy) OpenStreetMapTileImageProxyHandler(c *core.Context) (*httputil.ReverseProxy, *errs.Error) {
|
||||
director := func(req *http.Request) {
|
||||
zoomLevel := c.Param("zoomLevel")
|
||||
coordinateX := c.Param("coordinateX")
|
||||
fileName := c.Param("fileName")
|
||||
|
||||
imageRawUrl := fmt.Sprintf(openStreetMapTileImageUrlFormat, zoomLevel, coordinateX, fileName)
|
||||
imageUrl, _ := url.Parse(imageRawUrl)
|
||||
|
||||
req.URL = imageUrl
|
||||
req.RequestURI = req.URL.RequestURI()
|
||||
req.Host = imageUrl.Host
|
||||
}
|
||||
|
||||
return &httputil.ReverseProxy{Director: director}, nil
|
||||
}
|
||||
+7
-14
@@ -48,7 +48,7 @@ func (a *TokensApi) TokenListHandler(c *core.Context) (interface{}, *errs.Error)
|
||||
ExpiredAt: token.ExpiredUnixTime,
|
||||
}
|
||||
|
||||
if utils.Int64ToString(token.Uid) == claims.Id && utils.Int64ToString(token.UserTokenId) == claims.UserTokenId && token.CreatedUnixTime == claims.IssuedAt {
|
||||
if token.Uid == claims.Uid && utils.Int64ToString(token.UserTokenId) == claims.UserTokenId && token.CreatedUnixTime == claims.IssuedAt {
|
||||
tokenResp.IsCurrent = true
|
||||
}
|
||||
|
||||
@@ -62,17 +62,10 @@ func (a *TokensApi) TokenListHandler(c *core.Context) (interface{}, *errs.Error)
|
||||
|
||||
// TokenRevokeCurrentHandler revokes current token of current user
|
||||
func (a *TokensApi) TokenRevokeCurrentHandler(c *core.Context) (interface{}, *errs.Error) {
|
||||
_, claims, err := a.tokens.ParseToken(c)
|
||||
_, claims, err := a.tokens.ParseTokenByHeader(c)
|
||||
|
||||
if err != nil {
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
}
|
||||
|
||||
uid, err := utils.StringToInt64(claims.Id)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[tokens.TokenRevokeCurrentHandler] parse user id failed, because %s", err.Error())
|
||||
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||
return nil, errs.Or(err, errs.NewIncompleteOrIncorrectSubmissionError(err))
|
||||
}
|
||||
|
||||
userTokenId, err := utils.StringToInt64(claims.UserTokenId)
|
||||
@@ -83,7 +76,7 @@ func (a *TokensApi) TokenRevokeCurrentHandler(c *core.Context) (interface{}, *er
|
||||
}
|
||||
|
||||
tokenRecord := &models.TokenRecord{
|
||||
Uid: uid,
|
||||
Uid: claims.Uid,
|
||||
UserTokenId: userTokenId,
|
||||
CreatedUnixTime: claims.IssuedAt,
|
||||
}
|
||||
@@ -92,11 +85,11 @@ func (a *TokensApi) TokenRevokeCurrentHandler(c *core.Context) (interface{}, *er
|
||||
err = a.tokens.DeleteToken(tokenRecord)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[token.TokenRevokeCurrentHandler] failed to revoke token \"id:%s\" for user \"uid:%d\", because %s", tokenId, uid, err.Error())
|
||||
log.ErrorfWithRequestId(c, "[token.TokenRevokeCurrentHandler] failed to revoke token \"id:%s\" for user \"uid:%d\", because %s", tokenId, claims.Uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
log.InfofWithRequestId(c, "[token.TokenRevokeCurrentHandler] user \"uid:%d\" has revoked token \"id:%s\"", uid, tokenId)
|
||||
log.InfofWithRequestId(c, "[token.TokenRevokeCurrentHandler] user \"uid:%d\" has revoked token \"id:%s\"", claims.Uid, tokenId)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -154,7 +147,7 @@ func (a *TokensApi) TokenRevokeAllHandler(c *core.Context) (interface{}, *errs.E
|
||||
for i := 0; i < len(tokens); i++ {
|
||||
token := tokens[i]
|
||||
|
||||
if utils.Int64ToString(token.Uid) == claims.Id && utils.Int64ToString(token.UserTokenId) == claims.UserTokenId && token.CreatedUnixTime == claims.IssuedAt {
|
||||
if token.Uid == claims.Uid && utils.Int64ToString(token.UserTokenId) == claims.UserTokenId && token.CreatedUnixTime == claims.IssuedAt {
|
||||
currentTokenIndex = i
|
||||
break
|
||||
}
|
||||
|
||||
@@ -62,6 +62,11 @@ func (a *TransactionsApi) TransactionCountHandler(c *core.Context) (interface{},
|
||||
|
||||
totalCount, err := a.transactions.GetTransactionCount(uid, transactionCountReq.MaxTime, transactionCountReq.MinTime, transactionCountReq.Type, allCategoryIds, allAccountIds, transactionCountReq.Keyword)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.TransactionCountHandler] failed to get transaction count for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
countResp := &models.TransactionCountResponse{
|
||||
TotalCount: totalCount,
|
||||
}
|
||||
@@ -229,6 +234,11 @@ func (a *TransactionsApi) TransactionStatisticsHandler(c *core.Context) (interfa
|
||||
uid := c.GetCurrentUid()
|
||||
totalAmounts, err := a.transactions.GetAccountsAndCategoriesTotalIncomeAndExpense(uid, statisticReq.StartTime, statisticReq.EndTime)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.TransactionStatisticsHandler] failed to get accounts and categories total income and expense for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
statisticResp := &models.TransactionStatisticResponse{
|
||||
StartTime: statisticReq.StartTime,
|
||||
EndTime: statisticReq.EndTime,
|
||||
@@ -394,6 +404,12 @@ func (a *TransactionsApi) TransactionMonthAmountsHandler(c *core.Context) (inter
|
||||
}
|
||||
|
||||
totalAmounts, err := a.transactions.GetAccountsMonthTotalIncomeAndExpense(uid, startTime, endTime, pageCountForLoadTransactionAmounts)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[transactions.TransactionMonthAmountsHandler] failed to get accounts month total income and expense for user \"uid:%d\", because %s", uid, err.Error())
|
||||
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||
}
|
||||
|
||||
amountsMap := make(map[string]map[string]*models.TransactionAmountsResponseItemAmountInfo)
|
||||
|
||||
for yearMonth, monthAccountsAmounts := range totalAmounts {
|
||||
@@ -723,6 +739,11 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.Context) (interface{}
|
||||
newTransaction.RelatedAccountAmount = transactionModifyReq.DestinationAmount
|
||||
}
|
||||
|
||||
if transactionModifyReq.GeoLocation != nil {
|
||||
newTransaction.GeoLongitude = transactionModifyReq.GeoLocation.Longitude
|
||||
newTransaction.GeoLatitude = transactionModifyReq.GeoLocation.Latitude
|
||||
}
|
||||
|
||||
if newTransaction.CategoryId == transaction.CategoryId &&
|
||||
utils.GetUnixTimeFromTransactionTime(newTransaction.TransactionTime) == utils.GetUnixTimeFromTransactionTime(transaction.TransactionTime) &&
|
||||
newTransaction.TimezoneUtcOffset == transaction.TimezoneUtcOffset &&
|
||||
@@ -732,6 +753,8 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.Context) (interface{}
|
||||
(transaction.Type != models.TRANSACTION_DB_TYPE_TRANSFER_OUT || newTransaction.RelatedAccountAmount == transaction.RelatedAccountAmount) &&
|
||||
newTransaction.HideAmount == transaction.HideAmount &&
|
||||
newTransaction.Comment == transaction.Comment &&
|
||||
newTransaction.GeoLongitude == transaction.GeoLongitude &&
|
||||
newTransaction.GeoLatitude == transaction.GeoLatitude &&
|
||||
utils.Int64SliceEquals(tagIds, transactionTagIds) {
|
||||
return nil, errs.ErrNothingWillBeUpdated
|
||||
}
|
||||
@@ -1047,5 +1070,10 @@ func (a *TransactionsApi) createNewTransactionModel(uid int64, transactionCreate
|
||||
transaction.RelatedAccountAmount = transactionCreateReq.DestinationAmount
|
||||
}
|
||||
|
||||
if transactionCreateReq.GeoLocation != nil {
|
||||
transaction.GeoLongitude = transactionCreateReq.GeoLocation.Longitude
|
||||
transaction.GeoLatitude = transactionCreateReq.GeoLocation.Latitude
|
||||
}
|
||||
|
||||
return transaction
|
||||
}
|
||||
|
||||
+57
-15
@@ -57,6 +57,7 @@ func (a *UsersApi) UserRegisterHandler(c *core.Context) (interface{}, *errs.Erro
|
||||
Email: userRegisterReq.Email,
|
||||
Nickname: userRegisterReq.Nickname,
|
||||
Password: userRegisterReq.Password,
|
||||
Language: userRegisterReq.Language,
|
||||
DefaultCurrency: userRegisterReq.DefaultCurrency,
|
||||
FirstDayOfWeek: userRegisterReq.FirstDayOfWeek,
|
||||
TransactionEditScope: models.TRANSACTION_EDIT_SCOPE_ALL,
|
||||
@@ -161,12 +162,6 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.Context) (interface{}, *errs
|
||||
anythingUpdate = true
|
||||
}
|
||||
|
||||
if userUpdateReq.DefaultCurrency != "" && userUpdateReq.DefaultCurrency != user.DefaultCurrency {
|
||||
user.DefaultCurrency = userUpdateReq.DefaultCurrency
|
||||
userNew.DefaultCurrency = userUpdateReq.DefaultCurrency
|
||||
anythingUpdate = true
|
||||
}
|
||||
|
||||
if userUpdateReq.DefaultAccountId > 0 && userUpdateReq.DefaultAccountId != user.DefaultAccountId {
|
||||
accounts, err := a.accounts.GetAccountsByAccountIds(uid, []int64{userUpdateReq.DefaultAccountId})
|
||||
|
||||
@@ -179,14 +174,6 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.Context) (interface{}, *errs
|
||||
anythingUpdate = true
|
||||
}
|
||||
|
||||
if userUpdateReq.FirstDayOfWeek != nil && *userUpdateReq.FirstDayOfWeek != user.FirstDayOfWeek {
|
||||
user.FirstDayOfWeek = *userUpdateReq.FirstDayOfWeek
|
||||
userNew.FirstDayOfWeek = *userUpdateReq.FirstDayOfWeek
|
||||
anythingUpdate = true
|
||||
} else {
|
||||
userNew.FirstDayOfWeek = models.WEEKDAY_INVALID
|
||||
}
|
||||
|
||||
if userUpdateReq.TransactionEditScope != nil && *userUpdateReq.TransactionEditScope != user.TransactionEditScope {
|
||||
user.TransactionEditScope = *userUpdateReq.TransactionEditScope
|
||||
userNew.TransactionEditScope = *userUpdateReq.TransactionEditScope
|
||||
@@ -195,11 +182,66 @@ func (a *UsersApi) UserUpdateProfileHandler(c *core.Context) (interface{}, *errs
|
||||
userNew.TransactionEditScope = models.TRANSACTION_EDIT_SCOPE_INVALID
|
||||
}
|
||||
|
||||
modifyUserLanguage := false
|
||||
|
||||
if userUpdateReq.Language != user.Language {
|
||||
user.Language = userUpdateReq.Language
|
||||
userNew.Language = userUpdateReq.Language
|
||||
modifyUserLanguage = true
|
||||
anythingUpdate = true
|
||||
}
|
||||
|
||||
if userUpdateReq.DefaultCurrency != "" && userUpdateReq.DefaultCurrency != user.DefaultCurrency {
|
||||
user.DefaultCurrency = userUpdateReq.DefaultCurrency
|
||||
userNew.DefaultCurrency = userUpdateReq.DefaultCurrency
|
||||
anythingUpdate = true
|
||||
}
|
||||
|
||||
if userUpdateReq.FirstDayOfWeek != nil && *userUpdateReq.FirstDayOfWeek != user.FirstDayOfWeek {
|
||||
user.FirstDayOfWeek = *userUpdateReq.FirstDayOfWeek
|
||||
userNew.FirstDayOfWeek = *userUpdateReq.FirstDayOfWeek
|
||||
anythingUpdate = true
|
||||
} else {
|
||||
userNew.FirstDayOfWeek = models.WEEKDAY_INVALID
|
||||
}
|
||||
|
||||
if userUpdateReq.LongDateFormat != nil && *userUpdateReq.LongDateFormat != user.LongDateFormat {
|
||||
user.LongDateFormat = *userUpdateReq.LongDateFormat
|
||||
userNew.LongDateFormat = *userUpdateReq.LongDateFormat
|
||||
anythingUpdate = true
|
||||
} else {
|
||||
userNew.LongDateFormat = models.LONG_DATE_FORMAT_INVALID
|
||||
}
|
||||
|
||||
if userUpdateReq.ShortDateFormat != nil && *userUpdateReq.ShortDateFormat != user.ShortDateFormat {
|
||||
user.ShortDateFormat = *userUpdateReq.ShortDateFormat
|
||||
userNew.ShortDateFormat = *userUpdateReq.ShortDateFormat
|
||||
anythingUpdate = true
|
||||
} else {
|
||||
userNew.ShortDateFormat = models.SHORT_DATE_FORMAT_INVALID
|
||||
}
|
||||
|
||||
if userUpdateReq.LongTimeFormat != nil && *userUpdateReq.LongTimeFormat != user.LongTimeFormat {
|
||||
user.LongTimeFormat = *userUpdateReq.LongTimeFormat
|
||||
userNew.LongTimeFormat = *userUpdateReq.LongTimeFormat
|
||||
anythingUpdate = true
|
||||
} else {
|
||||
userNew.LongTimeFormat = models.LONG_TIME_FORMAT_INVALID
|
||||
}
|
||||
|
||||
if userUpdateReq.ShortTimeFormat != nil && *userUpdateReq.ShortTimeFormat != user.ShortTimeFormat {
|
||||
user.ShortTimeFormat = *userUpdateReq.ShortTimeFormat
|
||||
userNew.ShortTimeFormat = *userUpdateReq.ShortTimeFormat
|
||||
anythingUpdate = true
|
||||
} else {
|
||||
userNew.ShortTimeFormat = models.SHORT_TIME_FORMAT_INVALID
|
||||
}
|
||||
|
||||
if !anythingUpdate {
|
||||
return nil, errs.ErrNothingWillBeUpdated
|
||||
}
|
||||
|
||||
keyProfileUpdated, err := a.users.UpdateUser(userNew)
|
||||
keyProfileUpdated, err := a.users.UpdateUser(userNew, modifyUserLanguage)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[users.UserUpdateProfileHandler] failed to update user \"uid:%d\", because %s", user.Uid, err.Error())
|
||||
|
||||
@@ -142,7 +142,7 @@ func (l *UserDataCli) ModifyUserPassword(c *cli.Context, username string, passwo
|
||||
Password: password,
|
||||
}
|
||||
|
||||
_, err = l.users.UpdateUser(userNew)
|
||||
_, err = l.users.UpdateUser(userNew, false)
|
||||
|
||||
if err != nil {
|
||||
log.BootErrorf("[user_data.ModifyUserPassword] failed to update user \"%s\" password, because %s", user.Username, err.Error())
|
||||
|
||||
+1
-7
@@ -61,13 +61,7 @@ func (c *Context) GetCurrentUid() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
uid, err := strconv.ParseInt(claims.Id, 10, 64)
|
||||
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return uid
|
||||
return claims.Uid
|
||||
}
|
||||
|
||||
// GetClientTimezoneOffset returns the client timezone offset
|
||||
|
||||
+8
-1
@@ -1,6 +1,10 @@
|
||||
package core
|
||||
|
||||
import "github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
import (
|
||||
"net/http/httputil"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
)
|
||||
|
||||
// MiddlewareHandlerFunc represents the middleware handler function
|
||||
type MiddlewareHandlerFunc func(*Context)
|
||||
@@ -10,3 +14,6 @@ type ApiHandlerFunc func(*Context) (interface{}, *errs.Error)
|
||||
|
||||
// DataHandlerFunc represents the handler function that returns byte array
|
||||
type DataHandlerFunc func(*Context) ([]byte, string, *errs.Error)
|
||||
|
||||
// ProxyHandlerFunc represents the reverse proxy handler function
|
||||
type ProxyHandlerFunc func(*Context) (*httputil.ReverseProxy, *errs.Error)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
// TokenType represents token type
|
||||
@@ -16,7 +18,43 @@ const (
|
||||
// UserTokenClaims represents user token
|
||||
type UserTokenClaims struct {
|
||||
UserTokenId string `json:"userTokenId"`
|
||||
Uid int64 `json:"jti,string"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Type TokenType `json:"type"`
|
||||
jwt.StandardClaims
|
||||
IssuedAt int64 `json:"iat"`
|
||||
ExpiresAt int64 `json:"exp"`
|
||||
}
|
||||
|
||||
// GetExpirationTime returns the expiration time of this token
|
||||
func (c *UserTokenClaims) GetExpirationTime() (*jwt.NumericDate, error) {
|
||||
return &jwt.NumericDate{
|
||||
Time: time.Unix(c.ExpiresAt, 0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetIssuedAt returns the issue time of this token
|
||||
func (c *UserTokenClaims) GetIssuedAt() (*jwt.NumericDate, error) {
|
||||
return &jwt.NumericDate{
|
||||
Time: time.Unix(c.IssuedAt, 0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetNotBefore returns the earliest valid time of this token
|
||||
func (c *UserTokenClaims) GetNotBefore() (*jwt.NumericDate, error) {
|
||||
return &jwt.NumericDate{}, nil
|
||||
}
|
||||
|
||||
// GetIssuer returns the issuer of this token
|
||||
func (c *UserTokenClaims) GetIssuer() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// GetSubject returns the subject of this token
|
||||
func (c *UserTokenClaims) GetSubject() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// GetAudience returns the audience of this token
|
||||
func (c *UserTokenClaims) GetAudience() (jwt.ClaimStrings, error) {
|
||||
return jwt.ClaimStrings{}, nil
|
||||
}
|
||||
|
||||
@@ -9,4 +9,5 @@ var (
|
||||
ErrGettingLocalAddress = NewSystemError(SystemSubcategorySetting, 2, http.StatusInternalServerError, "failed to get local address")
|
||||
ErrInvalidUuidMode = NewSystemError(SystemSubcategorySetting, 3, http.StatusInternalServerError, "invalid uuid mode")
|
||||
ErrInvalidExchangeRatesDataSource = NewSystemError(SystemSubcategorySetting, 4, http.StatusInternalServerError, "invalid exchange rates data source")
|
||||
ErrInvalidMapProvider = NewSystemError(SystemSubcategorySetting, 5, http.StatusInternalServerError, "invalid map provider")
|
||||
)
|
||||
|
||||
@@ -18,4 +18,5 @@ var (
|
||||
ErrInvalidUserTokenId = NewNormalError(NormalSubcategoryToken, 9, http.StatusBadRequest, "user token id is invalid")
|
||||
ErrTokenRecordNotFound = NewNormalError(NormalSubcategoryToken, 10, http.StatusBadRequest, "token is not found")
|
||||
ErrTokenExpired = NewNormalError(NormalSubcategoryToken, 11, http.StatusBadRequest, "token is expired")
|
||||
ErrTokenIsEmpty = NewNormalError(NormalSubcategoryToken, 12, http.StatusBadRequest, "token is empty")
|
||||
)
|
||||
|
||||
@@ -32,6 +32,9 @@ func InitializeExchangeRatesDataSource(config *settings.Config) error {
|
||||
} else if config.ExchangeRatesDataSource == settings.NationalBankOfPolandDataSource {
|
||||
Container.Current = &NationalBankOfPolandDataSource{}
|
||||
return nil
|
||||
} else if config.ExchangeRatesDataSource == settings.MonetaryAuthorityOfSingaporeDataSource {
|
||||
Container.Current = &MonetaryAuthorityOfSingaporeDataSource{}
|
||||
return nil
|
||||
}
|
||||
|
||||
return errs.ErrInvalidExchangeRatesDataSource
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
package exchangerates
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/validators"
|
||||
)
|
||||
|
||||
const monetaryAuthorityOfSingaporeExchangeRateUrl = "https://eservices.mas.gov.sg/api/action/datastore/search.json?resource_id=95932927-c8bc-4e7a-b484-68a66a24edfe&sort=end_of_day+desc&limit=1"
|
||||
const monetaryAuthorityOfSingaporeExchangeRateReferenceUrl = "https://eservices.mas.gov.sg/Statistics/msb/ExchangeRates.aspx"
|
||||
const monetaryAuthorityOfSingaporeDataSource = "Monetary Authority of Singapore"
|
||||
const monetaryAuthorityOfSingaporeBaseCurrency = "SGD"
|
||||
|
||||
const monetaryAuthorityOfSingaporeDataUpdateDateFormat = "2006-01-02 15"
|
||||
const monetaryAuthorityOfSingaporeDataUpdateDateTimezone = "Asia/Singapore"
|
||||
|
||||
// MonetaryAuthorityOfSingaporeDataSource defines the structure of exchange rates data source of Monetary Authority of Singapore
|
||||
type MonetaryAuthorityOfSingaporeDataSource struct {
|
||||
ExchangeRatesDataSource
|
||||
}
|
||||
|
||||
// MonetaryAuthorityOfSingaporeExchangeRateData represents the whole data from Monetary Authority of Singapore
|
||||
type MonetaryAuthorityOfSingaporeExchangeRateData struct {
|
||||
Success bool `json:"success"`
|
||||
Result *MonetaryAuthorityOfSingaporeResult `json:"result"`
|
||||
}
|
||||
|
||||
// MonetaryAuthorityOfSingaporeResult represents the actual result from Monetary Authority of Singapore
|
||||
type MonetaryAuthorityOfSingaporeResult struct {
|
||||
Records []MonetaryAuthorityOfSingaporeRecord `json:"records"`
|
||||
}
|
||||
|
||||
// MonetaryAuthorityOfSingaporeRecord represents the record from Monetary Authority of Singapore
|
||||
type MonetaryAuthorityOfSingaporeRecord map[string]string
|
||||
|
||||
// ToLatestExchangeRateResponse returns a view-object according to original data from Monetary Authority of Singapore
|
||||
func (e *MonetaryAuthorityOfSingaporeExchangeRateData) ToLatestExchangeRateResponse(c *core.Context) *models.LatestExchangeRateResponse {
|
||||
if !e.Success {
|
||||
log.ErrorfWithRequestId(c, "[monetary_authority_of_singapore_datasource.ToLatestExchangeRateResponse] response is not success")
|
||||
return nil
|
||||
}
|
||||
|
||||
if e.Result == nil {
|
||||
log.ErrorfWithRequestId(c, "[monetary_authority_of_singapore_datasource.ToLatestExchangeRateResponse] result is null")
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(e.Result.Records) < 1 {
|
||||
log.ErrorfWithRequestId(c, "[monetary_authority_of_singapore_datasource.ToLatestExchangeRateResponse] records is empty")
|
||||
return nil
|
||||
}
|
||||
|
||||
lastDayRecord := e.Result.Records[0]
|
||||
exchangeRates := make(models.LatestExchangeRateSlice, 0, len(lastDayRecord))
|
||||
latestUpdateDate := ""
|
||||
|
||||
for key, value := range lastDayRecord {
|
||||
if key == "end_of_day" {
|
||||
latestUpdateDate = value
|
||||
continue
|
||||
}
|
||||
|
||||
exchangeRate := e.parseExchangeRateResponse(c, key, value)
|
||||
|
||||
if exchangeRate == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
exchangeRates = append(exchangeRates, exchangeRate)
|
||||
}
|
||||
|
||||
timezone, err := time.LoadLocation(monetaryAuthorityOfSingaporeDataUpdateDateTimezone)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[monetary_authority_of_singapore_datasource.ToLatestExchangeRateResponse] failed to get timezone, timezone name is %s", monetaryAuthorityOfSingaporeDataUpdateDateTimezone)
|
||||
return nil
|
||||
}
|
||||
|
||||
updateDateTime := latestUpdateDate + " 12" // These rates are the average of buying and selling interbank rates quoted around midday in Singapore
|
||||
updateTime, err := time.ParseInLocation(monetaryAuthorityOfSingaporeDataUpdateDateFormat, updateDateTime, timezone)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[monetary_authority_of_singapore_datasource.ToLatestExchangeRateResponse] failed to parse update date, datetime is %s", updateDateTime)
|
||||
return nil
|
||||
}
|
||||
|
||||
latestExchangeRateResp := &models.LatestExchangeRateResponse{
|
||||
DataSource: monetaryAuthorityOfSingaporeDataSource,
|
||||
ReferenceUrl: monetaryAuthorityOfSingaporeExchangeRateReferenceUrl,
|
||||
UpdateTime: updateTime.Unix(),
|
||||
BaseCurrency: monetaryAuthorityOfSingaporeBaseCurrency,
|
||||
ExchangeRates: exchangeRates,
|
||||
}
|
||||
|
||||
return latestExchangeRateResp
|
||||
}
|
||||
|
||||
func (e *MonetaryAuthorityOfSingaporeExchangeRateData) parseExchangeRateResponse(c *core.Context, key string, value string) *models.LatestExchangeRate {
|
||||
if !strings.Contains(key, "_") {
|
||||
return nil
|
||||
}
|
||||
|
||||
items := strings.Split(key, "_")
|
||||
|
||||
if len(items) < 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
fromCurrencyCode := strings.ToUpper(items[0])
|
||||
toCurrencyCode := strings.ToUpper(items[1])
|
||||
|
||||
if _, exists := validators.AllCurrencyNames[fromCurrencyCode]; !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
if toCurrencyCode != monetaryAuthorityOfSingaporeBaseCurrency {
|
||||
return nil
|
||||
}
|
||||
|
||||
rate, err := utils.StringToFloat64(value)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[monetary_authority_of_singapore_datasource.parseExchangeRateResponse] failed to parse rate, rate is %s", value)
|
||||
return nil
|
||||
}
|
||||
|
||||
if rate <= 0 {
|
||||
log.WarnfWithRequestId(c, "[monetary_authority_of_singapore_datasource.parseExchangeRateResponse] rate is invalid, rate is %s", value)
|
||||
return nil
|
||||
}
|
||||
|
||||
finalRate := 1 / rate
|
||||
|
||||
if math.IsInf(finalRate, 0) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(items) == 3 && items[2] == "100" {
|
||||
finalRate = finalRate * 100
|
||||
}
|
||||
|
||||
return &models.LatestExchangeRate{
|
||||
Currency: fromCurrencyCode,
|
||||
Rate: utils.Float64ToString(finalRate),
|
||||
}
|
||||
}
|
||||
|
||||
// GetRequestUrls returns the Monetary Authority of Singapore data source urls
|
||||
func (e *MonetaryAuthorityOfSingaporeDataSource) GetRequestUrls() []string {
|
||||
return []string{monetaryAuthorityOfSingaporeExchangeRateUrl}
|
||||
}
|
||||
|
||||
// Parse returns the common response entity according to the Monetary Authority of Singapore data source raw response
|
||||
func (e *MonetaryAuthorityOfSingaporeDataSource) Parse(c *core.Context, content []byte) (*models.LatestExchangeRateResponse, error) {
|
||||
monetaryAuthorityOfSingaporeData := &MonetaryAuthorityOfSingaporeExchangeRateData{}
|
||||
err := json.Unmarshal(content, monetaryAuthorityOfSingaporeData)
|
||||
|
||||
if err != nil {
|
||||
log.ErrorfWithRequestId(c, "[monetary_authority_of_singapore_datasource.Parse] failed to parse json data, content is %s, because %s", string(content), err.Error())
|
||||
return nil, errs.ErrFailedToRequestRemoteApi
|
||||
}
|
||||
|
||||
latestExchangeRateResponse := monetaryAuthorityOfSingaporeData.ToLatestExchangeRateResponse(c)
|
||||
|
||||
if latestExchangeRateResponse == nil {
|
||||
log.ErrorfWithRequestId(c, "[monetary_authority_of_singapore_datasource.Parse] failed to parse latest exchange rate data, content is %s", string(content))
|
||||
return nil, errs.ErrFailedToRequestRemoteApi
|
||||
}
|
||||
|
||||
return latestExchangeRateResponse, nil
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
package exchangerates
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||
)
|
||||
|
||||
const monetaryAuthorityOfSingaporeMinimumRequiredContent = "{\n" +
|
||||
" \"success\": true,\n" +
|
||||
" \"result\": {\n" +
|
||||
" \"records\": [\n" +
|
||||
" {\n" +
|
||||
" \"end_of_day\": \"2023-05-26\",\n" +
|
||||
" \"usd_sgd\": \"1.3528\",\n" +
|
||||
" \"cny_sgd_100\": \"19.16\"\n" +
|
||||
" }\n" +
|
||||
" ]\n" +
|
||||
" }\n" +
|
||||
"}"
|
||||
|
||||
func TestMonetaryAuthorityOfSingaporeDataSource_StandardDataExtractBaseCurrency(t *testing.T) {
|
||||
dataSource := &MonetaryAuthorityOfSingaporeDataSource{}
|
||||
context := &core.Context{
|
||||
Context: &gin.Context{},
|
||||
}
|
||||
|
||||
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte(monetaryAuthorityOfSingaporeMinimumRequiredContent))
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, "SGD", actualLatestExchangeRateResponse.BaseCurrency)
|
||||
}
|
||||
|
||||
func TestMonetaryAuthorityOfSingaporeDataSource_StandardDataExtractExchangeRates(t *testing.T) {
|
||||
dataSource := &MonetaryAuthorityOfSingaporeDataSource{}
|
||||
context := &core.Context{
|
||||
Context: &gin.Context{},
|
||||
}
|
||||
|
||||
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte(monetaryAuthorityOfSingaporeMinimumRequiredContent))
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Contains(t, actualLatestExchangeRateResponse.ExchangeRates, &models.LatestExchangeRate{
|
||||
Currency: "USD",
|
||||
Rate: "0.7392075694855116",
|
||||
})
|
||||
assert.Contains(t, actualLatestExchangeRateResponse.ExchangeRates, &models.LatestExchangeRate{
|
||||
Currency: "CNY",
|
||||
Rate: "5.219206680584551",
|
||||
})
|
||||
}
|
||||
|
||||
func TestMonetaryAuthorityOfSingaporeDataSource_BlankContent(t *testing.T) {
|
||||
dataSource := &MonetaryAuthorityOfSingaporeDataSource{}
|
||||
context := &core.Context{
|
||||
Context: &gin.Context{},
|
||||
}
|
||||
|
||||
_, err := dataSource.Parse(context, []byte(""))
|
||||
assert.NotEqual(t, nil, err)
|
||||
}
|
||||
|
||||
func TestMonetaryAuthorityOfSingaporeDataSource_EmptyJsonObject(t *testing.T) {
|
||||
dataSource := &MonetaryAuthorityOfSingaporeDataSource{}
|
||||
context := &core.Context{
|
||||
Context: &gin.Context{},
|
||||
}
|
||||
|
||||
_, err := dataSource.Parse(context, []byte("{}"))
|
||||
assert.NotEqual(t, nil, err)
|
||||
}
|
||||
|
||||
func TestMonetaryAuthorityOfSingaporeDataSource_ResponseSuccessIsFalseObject(t *testing.T) {
|
||||
dataSource := &MonetaryAuthorityOfSingaporeDataSource{}
|
||||
context := &core.Context{
|
||||
Context: &gin.Context{},
|
||||
}
|
||||
|
||||
_, err := dataSource.Parse(context, []byte("{\n"+
|
||||
" \"success\": false,\n"+
|
||||
" \"result\": {\n"+
|
||||
" \"records\": [\n"+
|
||||
" {\n"+
|
||||
" \"end_of_day\": \"2023-05-26\",\n"+
|
||||
" \"usd_sgd\": \"1.3528\",\n"+
|
||||
" \"cny_sgd_100\": \"19.16\"\n"+
|
||||
" }\n"+
|
||||
" ]\n"+
|
||||
" }\n"+
|
||||
"}"))
|
||||
assert.NotEqual(t, nil, err)
|
||||
}
|
||||
|
||||
func TestMonetaryAuthorityOfSingaporeDataSource_NoResultContent(t *testing.T) {
|
||||
dataSource := &MonetaryAuthorityOfSingaporeDataSource{}
|
||||
context := &core.Context{
|
||||
Context: &gin.Context{},
|
||||
}
|
||||
|
||||
_, err := dataSource.Parse(context, []byte("{\n"+
|
||||
" \"success\": true\n"+
|
||||
"}"))
|
||||
assert.NotEqual(t, nil, err)
|
||||
}
|
||||
|
||||
func TestMonetaryAuthorityOfSingaporeDataSource_EmptyRecordContent(t *testing.T) {
|
||||
dataSource := &MonetaryAuthorityOfSingaporeDataSource{}
|
||||
context := &core.Context{
|
||||
Context: &gin.Context{},
|
||||
}
|
||||
|
||||
_, err := dataSource.Parse(context, []byte("{\n"+
|
||||
" \"success\": true,\n"+
|
||||
" \"result\": {\n"+
|
||||
" \"records\": [\n"+
|
||||
" ]\n"+
|
||||
" }\n"+
|
||||
"}"))
|
||||
assert.NotEqual(t, nil, err)
|
||||
}
|
||||
|
||||
func TestMonetaryAuthorityOfSingaporeDataSource_TargetCurrencyIsNotBaseCurrency(t *testing.T) {
|
||||
dataSource := &MonetaryAuthorityOfSingaporeDataSource{}
|
||||
context := &core.Context{
|
||||
Context: &gin.Context{},
|
||||
}
|
||||
|
||||
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("{\n"+
|
||||
" \"success\": true,\n"+
|
||||
" \"result\": {\n"+
|
||||
" \"records\": [\n"+
|
||||
" {\n"+
|
||||
" \"end_of_day\": \"2023-05-26\",\n"+
|
||||
" \"usd_cny\": \"1\""+
|
||||
" }\n"+
|
||||
" ]\n"+
|
||||
" }\n"+
|
||||
"}"))
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0)
|
||||
}
|
||||
|
||||
func TestMonetaryAuthorityOfSingaporeDataSource_InvalidCurrency(t *testing.T) {
|
||||
dataSource := &MonetaryAuthorityOfSingaporeDataSource{}
|
||||
context := &core.Context{
|
||||
Context: &gin.Context{},
|
||||
}
|
||||
|
||||
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("{\n"+
|
||||
" \"success\": true,\n"+
|
||||
" \"result\": {\n"+
|
||||
" \"records\": [\n"+
|
||||
" {\n"+
|
||||
" \"end_of_day\": \"2023-05-26\",\n"+
|
||||
" \"xxx_sgd\": \"1.3528\""+
|
||||
" }\n"+
|
||||
" ]\n"+
|
||||
" }\n"+
|
||||
"}"))
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0)
|
||||
}
|
||||
|
||||
func TestMonetaryAuthorityOfSingaporeDataSource_EmptyRate(t *testing.T) {
|
||||
dataSource := &MonetaryAuthorityOfSingaporeDataSource{}
|
||||
context := &core.Context{
|
||||
Context: &gin.Context{},
|
||||
}
|
||||
|
||||
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("{\n"+
|
||||
" \"success\": true,\n"+
|
||||
" \"result\": {\n"+
|
||||
" \"records\": [\n"+
|
||||
" {\n"+
|
||||
" \"end_of_day\": \"2023-05-26\",\n"+
|
||||
" \"usd_sgd\": \"\""+
|
||||
" }\n"+
|
||||
" ]\n"+
|
||||
" }\n"+
|
||||
"}"))
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0)
|
||||
}
|
||||
|
||||
func TestMonetaryAuthorityOfSingaporeDataSource_InvalidRate(t *testing.T) {
|
||||
dataSource := &MonetaryAuthorityOfSingaporeDataSource{}
|
||||
context := &core.Context{
|
||||
Context: &gin.Context{},
|
||||
}
|
||||
|
||||
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("{\n"+
|
||||
" \"success\": true,\n"+
|
||||
" \"result\": {\n"+
|
||||
" \"records\": [\n"+
|
||||
" {\n"+
|
||||
" \"end_of_day\": \"2023-05-26\",\n"+
|
||||
" \"usd_sgd\": null"+
|
||||
" }\n"+
|
||||
" ]\n"+
|
||||
" }\n"+
|
||||
"}"))
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"time"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||
@@ -10,11 +10,20 @@ import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
|
||||
// TokenSourceType represents token source
|
||||
type TokenSourceType byte
|
||||
|
||||
// Token source types
|
||||
const (
|
||||
TOKEN_SOURCE_TYPE_HEADER TokenSourceType = 1
|
||||
TOKEN_SOURCE_TYPE_ARGUMENT TokenSourceType = 2
|
||||
)
|
||||
|
||||
const tokenQueryStringParam = "token"
|
||||
|
||||
// JWTAuthorization verifies whether current request is valid by jwt token
|
||||
func JWTAuthorization(c *core.Context) {
|
||||
claims, err := getTokenClaims(c)
|
||||
claims, err := getTokenClaims(c, TOKEN_SOURCE_TYPE_HEADER)
|
||||
|
||||
if err != nil {
|
||||
utils.PrintJsonErrorResult(c, err)
|
||||
@@ -22,13 +31,38 @@ func JWTAuthorization(c *core.Context) {
|
||||
}
|
||||
|
||||
if claims.Type == core.USER_TOKEN_TYPE_REQUIRE_2FA {
|
||||
log.WarnfWithRequestId(c, "[authorization.JWTAuthorization] user \"uid:%s\" token requires 2fa", claims.Id)
|
||||
log.WarnfWithRequestId(c, "[authorization.JWTAuthorization] user \"uid:%d\" token requires 2fa", claims.Uid)
|
||||
utils.PrintJsonErrorResult(c, errs.ErrCurrentTokenRequire2FA)
|
||||
return
|
||||
}
|
||||
|
||||
if claims.Type != core.USER_TOKEN_TYPE_NORMAL {
|
||||
log.WarnfWithRequestId(c, "[authorization.JWTAuthorization] user \"uid:%s\" token type is invalid", claims.Id)
|
||||
log.WarnfWithRequestId(c, "[authorization.JWTAuthorization] user \"uid:%d\" token type is invalid", claims.Uid)
|
||||
utils.PrintJsonErrorResult(c, errs.ErrCurrentInvalidTokenType)
|
||||
return
|
||||
}
|
||||
|
||||
c.SetTokenClaims(claims)
|
||||
c.Next()
|
||||
}
|
||||
|
||||
// JWTAuthorizationByQueryString verifies whether current request is valid by jwt token
|
||||
func JWTAuthorizationByQueryString(c *core.Context) {
|
||||
claims, err := getTokenClaims(c, TOKEN_SOURCE_TYPE_ARGUMENT)
|
||||
|
||||
if err != nil {
|
||||
utils.PrintJsonErrorResult(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if claims.Type == core.USER_TOKEN_TYPE_REQUIRE_2FA {
|
||||
log.WarnfWithRequestId(c, "[authorization.JWTAuthorizationByQueryString] user \"uid:%d\" token requires 2fa", claims.Uid)
|
||||
utils.PrintJsonErrorResult(c, errs.ErrCurrentTokenRequire2FA)
|
||||
return
|
||||
}
|
||||
|
||||
if claims.Type != core.USER_TOKEN_TYPE_NORMAL {
|
||||
log.WarnfWithRequestId(c, "[authorization.JWTAuthorizationByQueryString] user \"uid:%d\" token type is invalid", claims.Uid)
|
||||
utils.PrintJsonErrorResult(c, errs.ErrCurrentInvalidTokenType)
|
||||
return
|
||||
}
|
||||
@@ -39,7 +73,7 @@ func JWTAuthorization(c *core.Context) {
|
||||
|
||||
// JWTTwoFactorAuthorization verifies whether current request is valid by 2fa passcode
|
||||
func JWTTwoFactorAuthorization(c *core.Context) {
|
||||
claims, err := getTokenClaims(c)
|
||||
claims, err := getTokenClaims(c, TOKEN_SOURCE_TYPE_HEADER)
|
||||
|
||||
if err != nil {
|
||||
utils.PrintJsonErrorResult(c, err)
|
||||
@@ -47,7 +81,7 @@ func JWTTwoFactorAuthorization(c *core.Context) {
|
||||
}
|
||||
|
||||
if claims.Type != core.USER_TOKEN_TYPE_REQUIRE_2FA {
|
||||
log.WarnfWithRequestId(c, "[authorization.JWTTwoFactorAuthorization] user \"uid:%s\" token is not need two factor authorization", claims.Id)
|
||||
log.WarnfWithRequestId(c, "[authorization.JWTTwoFactorAuthorization] user \"uid:%d\" token is not need two factor authorization", claims.Uid)
|
||||
utils.PrintJsonErrorResult(c, errs.ErrCurrentTokenNotRequire2FA)
|
||||
return
|
||||
}
|
||||
@@ -56,12 +90,12 @@ func JWTTwoFactorAuthorization(c *core.Context) {
|
||||
c.Next()
|
||||
}
|
||||
|
||||
func getTokenClaims(c *core.Context) (*core.UserTokenClaims, *errs.Error) {
|
||||
token, claims, err := services.Tokens.ParseToken(c)
|
||||
func getTokenClaims(c *core.Context, source TokenSourceType) (*core.UserTokenClaims, *errs.Error) {
|
||||
token, claims, err := parseToken(c, source)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[authorization.getTokenClaims] failed to parse token, because %s", err.Error())
|
||||
return nil, errs.ErrUnauthorizedAccess
|
||||
return nil, errs.Or(err, errs.ErrUnauthorizedAccess)
|
||||
}
|
||||
|
||||
if !token.Valid {
|
||||
@@ -69,15 +103,18 @@ func getTokenClaims(c *core.Context) (*core.UserTokenClaims, *errs.Error) {
|
||||
return nil, errs.ErrCurrentInvalidToken
|
||||
}
|
||||
|
||||
if !claims.VerifyExpiresAt(time.Now().Unix(), true) {
|
||||
log.WarnfWithRequestId(c, "[authorization.getTokenClaims] token is expired")
|
||||
return nil, errs.ErrCurrentTokenExpired
|
||||
}
|
||||
|
||||
if claims.Id == "" {
|
||||
log.WarnfWithRequestId(c, "[authorization.getTokenClaims] user id in token is empty")
|
||||
if claims.Uid <= 0 {
|
||||
log.WarnfWithRequestId(c, "[authorization.getTokenClaims] user id in token is invalid")
|
||||
return nil, errs.ErrCurrentInvalidToken
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func parseToken(c *core.Context, source TokenSourceType) (*jwt.Token, *core.UserTokenClaims, error) {
|
||||
if source == TOKEN_SOURCE_TYPE_ARGUMENT {
|
||||
return services.Tokens.ParseTokenByArgument(c, tokenQueryStringParam)
|
||||
}
|
||||
|
||||
return services.Tokens.ParseTokenByHeader(c)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
|
||||
// RequestLog logs the http request log
|
||||
@@ -28,7 +29,7 @@ func RequestLog(c *core.Context) {
|
||||
method := c.Request.Method
|
||||
|
||||
if claims != nil {
|
||||
userId = claims.Id
|
||||
userId = utils.Int64ToString(claims.Uid)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -16,6 +16,8 @@ func ServerSettingsCookie(config *settings.Config) core.MiddlewareHandlerFunc {
|
||||
settingsArr := []string{
|
||||
buildBooleanSetting("r", config.EnableUserRegister),
|
||||
buildBooleanSetting("e", config.EnableDataExport),
|
||||
buildStringSetting("m", config.MapProvider),
|
||||
buildBooleanSetting("mp", config.EnableMapDataFetchProxy),
|
||||
}
|
||||
|
||||
bundledSettings := strings.Join(settingsArr, "_")
|
||||
@@ -25,6 +27,10 @@ func ServerSettingsCookie(config *settings.Config) core.MiddlewareHandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func buildStringSetting(key string, value string) string {
|
||||
return fmt.Sprintf("%s.%s", key, strings.Replace(value, ".", "-", -1))
|
||||
}
|
||||
|
||||
func buildBooleanSetting(key string, value bool) string {
|
||||
if value {
|
||||
return fmt.Sprintf("%s.1", key)
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
package models
|
||||
|
||||
import "fmt"
|
||||
|
||||
// WeekDay represents week day
|
||||
type WeekDay byte
|
||||
|
||||
// Week days
|
||||
const (
|
||||
WEEKDAY_SUNDAY WeekDay = 0
|
||||
WEEKDAY_MONDAY WeekDay = 1
|
||||
WEEKDAY_TUESDAY WeekDay = 2
|
||||
WEEKDAY_WEDNESDAY WeekDay = 3
|
||||
WEEKDAY_THURSDAY WeekDay = 4
|
||||
WEEKDAY_FRIDAY WeekDay = 5
|
||||
WEEKDAY_SATURDAY WeekDay = 6
|
||||
WEEKDAY_INVALID WeekDay = 255
|
||||
)
|
||||
|
||||
// String returns a textual representation of the week day enum
|
||||
func (d WeekDay) String() string {
|
||||
switch d {
|
||||
case WEEKDAY_SUNDAY:
|
||||
return "Sunday"
|
||||
case WEEKDAY_MONDAY:
|
||||
return "Monday"
|
||||
case WEEKDAY_TUESDAY:
|
||||
return "Tuesday"
|
||||
case WEEKDAY_WEDNESDAY:
|
||||
return "Wednesday"
|
||||
case WEEKDAY_THURSDAY:
|
||||
return "Thursday"
|
||||
case WEEKDAY_FRIDAY:
|
||||
return "Friday"
|
||||
case WEEKDAY_SATURDAY:
|
||||
return "Saturday"
|
||||
case WEEKDAY_INVALID:
|
||||
return "Invalid"
|
||||
default:
|
||||
return fmt.Sprintf("Invalid(%d)", int(d))
|
||||
}
|
||||
}
|
||||
|
||||
// LongDateFormat represents long date format
|
||||
type LongDateFormat byte
|
||||
|
||||
// Long Date Format
|
||||
const (
|
||||
LONG_DATE_FORMAT_DEFAULT LongDateFormat = 0
|
||||
LONG_DATE_FORMAT_YYYY_M_D LongDateFormat = 1
|
||||
LONG_DATE_FORMAT_M_D_YYYY LongDateFormat = 2
|
||||
LONG_DATE_FORMAT_D_M_YYYY LongDateFormat = 3
|
||||
LONG_DATE_FORMAT_INVALID LongDateFormat = 255
|
||||
)
|
||||
|
||||
// String returns a textual representation of the long date format enum
|
||||
func (f LongDateFormat) String() string {
|
||||
switch f {
|
||||
case LONG_DATE_FORMAT_DEFAULT:
|
||||
return "Default"
|
||||
case LONG_DATE_FORMAT_YYYY_M_D:
|
||||
return "YYYY_MM_D"
|
||||
case LONG_DATE_FORMAT_M_D_YYYY:
|
||||
return "M_D_YYYY"
|
||||
case LONG_DATE_FORMAT_D_M_YYYY:
|
||||
return "D_M_YYYY"
|
||||
case LONG_DATE_FORMAT_INVALID:
|
||||
return "Invalid"
|
||||
default:
|
||||
return fmt.Sprintf("Invalid(%d)", int(f))
|
||||
}
|
||||
}
|
||||
|
||||
// ShortDateFormat represents short date format
|
||||
type ShortDateFormat byte
|
||||
|
||||
// Short Date Format
|
||||
const (
|
||||
SHORT_DATE_FORMAT_DEFAULT ShortDateFormat = 0
|
||||
SHORT_DATE_FORMAT_YYYY_M_D ShortDateFormat = 1
|
||||
SHORT_DATE_FORMAT_M_D_YYYY ShortDateFormat = 2
|
||||
SHORT_DATE_FORMAT_D_M_YYYY ShortDateFormat = 3
|
||||
SHORT_DATE_FORMAT_INVALID ShortDateFormat = 255
|
||||
)
|
||||
|
||||
// String returns a textual representation of the short date format enum
|
||||
func (f ShortDateFormat) String() string {
|
||||
switch f {
|
||||
case SHORT_DATE_FORMAT_DEFAULT:
|
||||
return "Default"
|
||||
case SHORT_DATE_FORMAT_YYYY_M_D:
|
||||
return "YYYY_MM_D"
|
||||
case SHORT_DATE_FORMAT_M_D_YYYY:
|
||||
return "M_D_YYYY"
|
||||
case SHORT_DATE_FORMAT_D_M_YYYY:
|
||||
return "D_M_YYYY"
|
||||
case SHORT_DATE_FORMAT_INVALID:
|
||||
return "Invalid"
|
||||
default:
|
||||
return fmt.Sprintf("Invalid(%d)", int(f))
|
||||
}
|
||||
}
|
||||
|
||||
// LongTimeFormat represents long time format
|
||||
type LongTimeFormat byte
|
||||
|
||||
// Long Time Format
|
||||
const (
|
||||
LONG_TIME_FORMAT_DEFAULT LongTimeFormat = 0
|
||||
LONG_TIME_FORMAT_HH_MM_SS LongTimeFormat = 1
|
||||
LONG_TIME_FORMAT_A_HH_MM_SS LongTimeFormat = 2
|
||||
LONG_TIME_FORMAT_HH_MM_SS_A LongTimeFormat = 3
|
||||
LONG_TIME_FORMAT_INVALID LongTimeFormat = 255
|
||||
)
|
||||
|
||||
// String returns a textual representation of the long time format enum
|
||||
func (f LongTimeFormat) String() string {
|
||||
switch f {
|
||||
case LONG_TIME_FORMAT_DEFAULT:
|
||||
return "Default"
|
||||
case LONG_TIME_FORMAT_HH_MM_SS:
|
||||
return "HH_MM_SS"
|
||||
case LONG_TIME_FORMAT_A_HH_MM_SS:
|
||||
return "A_HH_MM_SS"
|
||||
case LONG_TIME_FORMAT_HH_MM_SS_A:
|
||||
return "HH_MM_SS_A"
|
||||
case LONG_TIME_FORMAT_INVALID:
|
||||
return "Invalid"
|
||||
default:
|
||||
return fmt.Sprintf("Invalid(%d)", int(f))
|
||||
}
|
||||
}
|
||||
|
||||
// ShortTimeFormat represents short time format
|
||||
type ShortTimeFormat byte
|
||||
|
||||
// Short Time Format
|
||||
const (
|
||||
SHORT_TIME_FORMAT_DEFAULT ShortTimeFormat = 0
|
||||
SHORT_TIME_FORMAT_HH_MM ShortTimeFormat = 1
|
||||
SHORT_TIME_FORMAT_A_HH_MM ShortTimeFormat = 2
|
||||
SHORT_TIME_FORMAT_HH_MM_A ShortTimeFormat = 3
|
||||
SHORT_TIME_FORMAT_INVALID ShortTimeFormat = 255
|
||||
)
|
||||
|
||||
// String returns a textual representation of the short time format enum
|
||||
func (f ShortTimeFormat) String() string {
|
||||
switch f {
|
||||
case SHORT_TIME_FORMAT_DEFAULT:
|
||||
return "Default"
|
||||
case SHORT_TIME_FORMAT_HH_MM:
|
||||
return "HH_MM"
|
||||
case SHORT_TIME_FORMAT_A_HH_MM:
|
||||
return "A_HH_MM"
|
||||
case SHORT_TIME_FORMAT_HH_MM_A:
|
||||
return "HH_MM_A"
|
||||
case SHORT_TIME_FORMAT_INVALID:
|
||||
return "Invalid"
|
||||
default:
|
||||
return fmt.Sprintf("Invalid(%d)", int(f))
|
||||
}
|
||||
}
|
||||
+51
-24
@@ -35,8 +35,8 @@ const (
|
||||
// Transaction represents transaction data stored in database
|
||||
type Transaction struct {
|
||||
TransactionId int64 `xorm:"PK"`
|
||||
Uid int64 `xorm:"UNIQUE(UQE_transaction_uid_time) INDEX(IDX_transaction_uid_deleted_time) INDEX(IDX_transaction_uid_deleted_type_time) INDEX(IDX_transaction_uid_deleted_category_id_time) INDEX(IDX_transaction_uid_deleted_account_id_time) NOT NULL"`
|
||||
Deleted bool `xorm:"INDEX(IDX_transaction_uid_deleted_time) INDEX(IDX_transaction_uid_deleted_type_time) INDEX(IDX_transaction_uid_deleted_category_id_time) INDEX(IDX_transaction_uid_deleted_account_id_time) NOT NULL"`
|
||||
Uid int64 `xorm:"UNIQUE(UQE_transaction_uid_time) INDEX(IDX_transaction_uid_deleted_time) INDEX(IDX_transaction_uid_deleted_type_time) INDEX(IDX_transaction_uid_deleted_category_id_time) INDEX(IDX_transaction_uid_deleted_account_id_time) INDEX(IDX_transaction_uid_deleted_time_longitude_latitude) NOT NULL"`
|
||||
Deleted bool `xorm:"INDEX(IDX_transaction_uid_deleted_time) INDEX(IDX_transaction_uid_deleted_type_time) INDEX(IDX_transaction_uid_deleted_category_id_time) INDEX(IDX_transaction_uid_deleted_account_id_time) INDEX(IDX_transaction_uid_deleted_time_longitude_latitude) NOT NULL"`
|
||||
Type TransactionDbType `xorm:"INDEX(IDX_transaction_uid_deleted_type_time) NOT NULL"`
|
||||
CategoryId int64 `xorm:"INDEX(IDX_transaction_uid_deleted_category_id_time) NOT NULL"`
|
||||
AccountId int64 `xorm:"INDEX(IDX_transaction_uid_deleted_account_id_time) NOT NULL"`
|
||||
@@ -48,40 +48,50 @@ type Transaction struct {
|
||||
RelatedAccountAmount int64 `xorm:"NOT NULL"`
|
||||
HideAmount bool `xorm:"NOT NULL"`
|
||||
Comment string `xorm:"VARCHAR(255) NOT NULL"`
|
||||
GeoLongitude float64 `xorm:"INDEX(IDX_transaction_uid_deleted_time_longitude_latitude)"`
|
||||
GeoLatitude float64 `xorm:"INDEX(IDX_transaction_uid_deleted_time_longitude_latitude)"`
|
||||
CreatedIp string `xorm:"VARCHAR(39)"`
|
||||
CreatedUnixTime int64
|
||||
UpdatedUnixTime int64
|
||||
DeletedUnixTime int64
|
||||
}
|
||||
|
||||
// TransactionGeoLocationRequest represents all parameters of transaction geographic location info update request
|
||||
type TransactionGeoLocationRequest struct {
|
||||
Latitude float64 `json:"latitude" binding:"required"`
|
||||
Longitude float64 `json:"longitude" binding:"required"`
|
||||
}
|
||||
|
||||
// TransactionCreateRequest represents all parameters of transaction creation request
|
||||
type TransactionCreateRequest struct {
|
||||
Type TransactionType `json:"type" binding:"required"`
|
||||
CategoryId int64 `json:"categoryId,string"`
|
||||
Time int64 `json:"time" binding:"required,min=1"`
|
||||
UtcOffset int16 `json:"utcOffset" binding:"min=-720,max=840"`
|
||||
SourceAccountId int64 `json:"sourceAccountId,string" binding:"required,min=1"`
|
||||
DestinationAccountId int64 `json:"destinationAccountId,string" binding:"min=0"`
|
||||
SourceAmount int64 `json:"sourceAmount" binding:"min=-99999999999,max=99999999999"`
|
||||
DestinationAmount int64 `json:"destinationAmount" binding:"min=-99999999999,max=99999999999"`
|
||||
HideAmount bool `json:"hideAmount"`
|
||||
TagIds []string `json:"tagIds"`
|
||||
Comment string `json:"comment" binding:"max=255"`
|
||||
Type TransactionType `json:"type" binding:"required"`
|
||||
CategoryId int64 `json:"categoryId,string"`
|
||||
Time int64 `json:"time" binding:"required,min=1"`
|
||||
UtcOffset int16 `json:"utcOffset" binding:"min=-720,max=840"`
|
||||
SourceAccountId int64 `json:"sourceAccountId,string" binding:"required,min=1"`
|
||||
DestinationAccountId int64 `json:"destinationAccountId,string" binding:"min=0"`
|
||||
SourceAmount int64 `json:"sourceAmount" binding:"min=-99999999999,max=99999999999"`
|
||||
DestinationAmount int64 `json:"destinationAmount" binding:"min=-99999999999,max=99999999999"`
|
||||
HideAmount bool `json:"hideAmount"`
|
||||
TagIds []string `json:"tagIds"`
|
||||
Comment string `json:"comment" binding:"max=255"`
|
||||
GeoLocation *TransactionGeoLocationRequest `json:"geoLocation" binding:"omitempty"`
|
||||
}
|
||||
|
||||
// TransactionModifyRequest represents all parameters of transaction modification request
|
||||
type TransactionModifyRequest struct {
|
||||
Id int64 `json:"id,string" binding:"required,min=1"`
|
||||
CategoryId int64 `json:"categoryId,string"`
|
||||
Time int64 `json:"time" binding:"required,min=1"`
|
||||
UtcOffset int16 `json:"utcOffset" binding:"min=-720,max=840"`
|
||||
SourceAccountId int64 `json:"sourceAccountId,string" binding:"required,min=1"`
|
||||
DestinationAccountId int64 `json:"destinationAccountId,string" binding:"min=0"`
|
||||
SourceAmount int64 `json:"sourceAmount" binding:"min=-99999999999,max=99999999999"`
|
||||
DestinationAmount int64 `json:"destinationAmount" binding:"min=-99999999999,max=99999999999"`
|
||||
HideAmount bool `json:"hideAmount"`
|
||||
TagIds []string `json:"tagIds"`
|
||||
Comment string `json:"comment" binding:"max=255"`
|
||||
Id int64 `json:"id,string" binding:"required,min=1"`
|
||||
CategoryId int64 `json:"categoryId,string"`
|
||||
Time int64 `json:"time" binding:"required,min=1"`
|
||||
UtcOffset int16 `json:"utcOffset" binding:"min=-720,max=840"`
|
||||
SourceAccountId int64 `json:"sourceAccountId,string" binding:"required,min=1"`
|
||||
DestinationAccountId int64 `json:"destinationAccountId,string" binding:"min=0"`
|
||||
SourceAmount int64 `json:"sourceAmount" binding:"min=-99999999999,max=99999999999"`
|
||||
DestinationAmount int64 `json:"destinationAmount" binding:"min=-99999999999,max=99999999999"`
|
||||
HideAmount bool `json:"hideAmount"`
|
||||
TagIds []string `json:"tagIds"`
|
||||
Comment string `json:"comment" binding:"max=255"`
|
||||
GeoLocation *TransactionGeoLocationRequest `json:"geoLocation" binding:"omitempty"`
|
||||
}
|
||||
|
||||
// TransactionCountRequest represents transaction count request
|
||||
@@ -170,6 +180,12 @@ type TransactionAccountAmount struct {
|
||||
TotalExpenseAmount int64
|
||||
}
|
||||
|
||||
// TransactionGeoLocationResponse represents a view-object of transaction geographic location info
|
||||
type TransactionGeoLocationResponse struct {
|
||||
Latitude float64 `json:"latitude"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
}
|
||||
|
||||
// TransactionInfoResponse represents a view-object of transaction
|
||||
type TransactionInfoResponse struct {
|
||||
Id int64 `json:"id,string"`
|
||||
@@ -189,6 +205,7 @@ type TransactionInfoResponse struct {
|
||||
TagIds []string `json:"tagIds"`
|
||||
Tags []*TransactionTagInfoResponse `json:"tags,omitempty"`
|
||||
Comment string `json:"comment"`
|
||||
GeoLocation *TransactionGeoLocationResponse `json:"geoLocation,omitempty"`
|
||||
Editable bool `json:"editable"`
|
||||
}
|
||||
|
||||
@@ -298,6 +315,15 @@ func (t *Transaction) ToTransactionInfoResponse(tagIds []int64, editable bool) *
|
||||
destinationAmount = t.Amount
|
||||
}
|
||||
|
||||
geoLocation := &TransactionGeoLocationResponse{}
|
||||
|
||||
if t.GeoLongitude != 0 || t.GeoLatitude != 0 {
|
||||
geoLocation.Longitude = t.GeoLongitude
|
||||
geoLocation.Latitude = t.GeoLatitude
|
||||
} else {
|
||||
geoLocation = nil
|
||||
}
|
||||
|
||||
return &TransactionInfoResponse{
|
||||
Id: t.TransactionId,
|
||||
TimeSequenceId: t.TransactionTime,
|
||||
@@ -312,6 +338,7 @@ func (t *Transaction) ToTransactionInfoResponse(tagIds []int64, editable bool) *
|
||||
HideAmount: t.HideAmount,
|
||||
TagIds: utils.Int64ArrayToStringArray(tagIds),
|
||||
Comment: t.Comment,
|
||||
GeoLocation: geoLocation,
|
||||
Editable: editable,
|
||||
}
|
||||
}
|
||||
|
||||
+43
-51
@@ -7,45 +7,6 @@ import (
|
||||
"github.com/mayswind/ezbookkeeping/pkg/utils"
|
||||
)
|
||||
|
||||
// WeekDay represents week day
|
||||
type WeekDay byte
|
||||
|
||||
// Week days
|
||||
const (
|
||||
WEEKDAY_SUNDAY WeekDay = 0
|
||||
WEEKDAY_MONDAY WeekDay = 1
|
||||
WEEKDAY_TUESDAY WeekDay = 2
|
||||
WEEKDAY_WEDNESDAY WeekDay = 3
|
||||
WEEKDAY_THURSDAY WeekDay = 4
|
||||
WEEKDAY_FRIDAY WeekDay = 5
|
||||
WEEKDAY_SATURDAY WeekDay = 6
|
||||
WEEKDAY_INVALID WeekDay = 255
|
||||
)
|
||||
|
||||
// String returns a textual representation of the week day enum
|
||||
func (d WeekDay) String() string {
|
||||
switch d {
|
||||
case WEEKDAY_SUNDAY:
|
||||
return "Sunday"
|
||||
case WEEKDAY_MONDAY:
|
||||
return "Monday"
|
||||
case WEEKDAY_TUESDAY:
|
||||
return "Tuesday"
|
||||
case WEEKDAY_WEDNESDAY:
|
||||
return "Wednesday"
|
||||
case WEEKDAY_THURSDAY:
|
||||
return "Thursday"
|
||||
case WEEKDAY_FRIDAY:
|
||||
return "Friday"
|
||||
case WEEKDAY_SATURDAY:
|
||||
return "Saturday"
|
||||
case WEEKDAY_INVALID:
|
||||
return "Invalid"
|
||||
default:
|
||||
return fmt.Sprintf("Invalid(%d)", int(d))
|
||||
}
|
||||
}
|
||||
|
||||
// TransactionEditScope represents the scope which transaction can be edited
|
||||
type TransactionEditScope byte
|
||||
|
||||
@@ -93,10 +54,15 @@ type User struct {
|
||||
Nickname string `xorm:"VARCHAR(64) NOT NULL"`
|
||||
Password string `xorm:"VARCHAR(64) NOT NULL"`
|
||||
Salt string `xorm:"VARCHAR(10) NOT NULL"`
|
||||
DefaultCurrency string `xorm:"VARCHAR(3) NOT NULL"`
|
||||
DefaultAccountId int64
|
||||
FirstDayOfWeek WeekDay `xorm:"TINYINT NOT NULL"`
|
||||
TransactionEditScope TransactionEditScope `xorm:"TINYINT NOT NULL"`
|
||||
Language string `xorm:"VARCHAR(10)"`
|
||||
DefaultCurrency string `xorm:"VARCHAR(3) NOT NULL"`
|
||||
FirstDayOfWeek WeekDay `xorm:"TINYINT NOT NULL"`
|
||||
LongDateFormat LongDateFormat `xorm:"TINYINT"`
|
||||
ShortDateFormat ShortDateFormat `xorm:"TINYINT"`
|
||||
LongTimeFormat LongTimeFormat `xorm:"TINYINT"`
|
||||
ShortTimeFormat ShortTimeFormat `xorm:"TINYINT"`
|
||||
Deleted bool `xorm:"NOT NULL"`
|
||||
EmailVerified bool `xorm:"NOT NULL"`
|
||||
CreatedUnixTime int64
|
||||
@@ -110,10 +76,15 @@ type UserBasicInfo struct {
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Nickname string `json:"nickname"`
|
||||
DefaultCurrency string `json:"defaultCurrency"`
|
||||
DefaultAccountId int64 `json:"defaultAccountId,string"`
|
||||
FirstDayOfWeek WeekDay `json:"firstDayOfWeek"`
|
||||
TransactionEditScope TransactionEditScope `json:"transactionEditScope"`
|
||||
Language string `json:"language"`
|
||||
DefaultCurrency string `json:"defaultCurrency"`
|
||||
FirstDayOfWeek WeekDay `json:"firstDayOfWeek"`
|
||||
LongDateFormat LongDateFormat `json:"longDateFormat"`
|
||||
ShortDateFormat ShortDateFormat `json:"shortDateFormat"`
|
||||
LongTimeFormat LongTimeFormat `json:"longTimeFormat"`
|
||||
ShortTimeFormat ShortTimeFormat `json:"shortTimeFormat"`
|
||||
}
|
||||
|
||||
// UserLoginRequest represents all parameters of user login request
|
||||
@@ -128,6 +99,7 @@ type UserRegisterRequest struct {
|
||||
Email string `json:"email" binding:"required,notBlank,max=100,validEmail"`
|
||||
Nickname string `json:"nickname" binding:"required,notBlank,max=64"`
|
||||
Password string `json:"password" binding:"required,min=6,max=128"`
|
||||
Language string `json:"language" binding:"required,min=2,max=16"`
|
||||
DefaultCurrency string `json:"defaultCurrency" binding:"required,len=3,validCurrency"`
|
||||
FirstDayOfWeek WeekDay `json:"firstDayOfWeek" binding:"min=0,max=6"`
|
||||
}
|
||||
@@ -138,10 +110,15 @@ type UserProfileUpdateRequest struct {
|
||||
Nickname string `json:"nickname" binding:"omitempty,notBlank,max=64"`
|
||||
Password string `json:"password" binding:"omitempty,min=6,max=128"`
|
||||
OldPassword string `json:"oldPassword" binding:"omitempty,min=6,max=128"`
|
||||
DefaultCurrency string `json:"defaultCurrency" binding:"omitempty,len=3,validCurrency"`
|
||||
DefaultAccountId int64 `json:"defaultAccountId,string" binding:"omitempty,min=1"`
|
||||
FirstDayOfWeek *WeekDay `json:"firstDayOfWeek" binding:"omitempty,min=0,max=6"`
|
||||
TransactionEditScope *TransactionEditScope `json:"transactionEditScope" binding:"omitempty,min=0,max=7"`
|
||||
Language string `json:"language" binding:"omitempty,min=2,max=16"`
|
||||
DefaultCurrency string `json:"defaultCurrency" binding:"omitempty,len=3,validCurrency"`
|
||||
FirstDayOfWeek *WeekDay `json:"firstDayOfWeek" binding:"omitempty,min=0,max=6"`
|
||||
LongDateFormat *LongDateFormat `json:"longDateFormat" binding:"omitempty,min=0,max=3"`
|
||||
ShortDateFormat *ShortDateFormat `json:"shortDateFormat" binding:"omitempty,min=0,max=3"`
|
||||
LongTimeFormat *LongTimeFormat `json:"longTimeFormat" binding:"omitempty,min=0,max=3"`
|
||||
ShortTimeFormat *ShortTimeFormat `json:"shortTimeFormat" binding:"omitempty,min=0,max=3"`
|
||||
}
|
||||
|
||||
// UserProfileUpdateResponse represents the data returns to frontend after updating profile
|
||||
@@ -155,10 +132,15 @@ type UserProfileResponse struct {
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Nickname string `json:"nickname"`
|
||||
DefaultCurrency string `json:"defaultCurrency"`
|
||||
DefaultAccountId int64 `json:"defaultAccountId,string"`
|
||||
FirstDayOfWeek WeekDay `json:"firstDayOfWeek"`
|
||||
TransactionEditScope TransactionEditScope `json:"transactionEditScope"`
|
||||
Language string `json:"language"`
|
||||
DefaultCurrency string `json:"defaultCurrency"`
|
||||
FirstDayOfWeek WeekDay `json:"firstDayOfWeek"`
|
||||
LongDateFormat LongDateFormat `json:"longDateFormat"`
|
||||
ShortDateFormat ShortDateFormat `json:"shortDateFormat"`
|
||||
LongTimeFormat LongTimeFormat `json:"longTimeFormat"`
|
||||
ShortTimeFormat ShortTimeFormat `json:"shortTimeFormat"`
|
||||
LastLoginAt int64 `json:"lastLoginAt"`
|
||||
}
|
||||
|
||||
@@ -210,10 +192,15 @@ func (u *User) ToUserBasicInfo() *UserBasicInfo {
|
||||
Username: u.Username,
|
||||
Email: u.Email,
|
||||
Nickname: u.Nickname,
|
||||
DefaultCurrency: u.DefaultCurrency,
|
||||
DefaultAccountId: u.DefaultAccountId,
|
||||
FirstDayOfWeek: u.FirstDayOfWeek,
|
||||
TransactionEditScope: u.TransactionEditScope,
|
||||
Language: u.Language,
|
||||
DefaultCurrency: u.DefaultCurrency,
|
||||
FirstDayOfWeek: u.FirstDayOfWeek,
|
||||
LongDateFormat: u.LongDateFormat,
|
||||
ShortDateFormat: u.ShortDateFormat,
|
||||
LongTimeFormat: u.LongTimeFormat,
|
||||
ShortTimeFormat: u.ShortTimeFormat,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,10 +210,15 @@ func (u *User) ToUserProfileResponse() *UserProfileResponse {
|
||||
Username: u.Username,
|
||||
Email: u.Email,
|
||||
Nickname: u.Nickname,
|
||||
DefaultCurrency: u.DefaultCurrency,
|
||||
DefaultAccountId: u.DefaultAccountId,
|
||||
FirstDayOfWeek: u.FirstDayOfWeek,
|
||||
TransactionEditScope: u.TransactionEditScope,
|
||||
Language: u.Language,
|
||||
DefaultCurrency: u.DefaultCurrency,
|
||||
FirstDayOfWeek: u.FirstDayOfWeek,
|
||||
LongDateFormat: u.LongDateFormat,
|
||||
ShortDateFormat: u.ShortDateFormat,
|
||||
LongTimeFormat: u.LongTimeFormat,
|
||||
ShortTimeFormat: u.ShortTimeFormat,
|
||||
LastLoginAt: u.LastLoginUnixTime,
|
||||
}
|
||||
}
|
||||
|
||||
+73
-54
@@ -6,8 +6,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/golang-jwt/jwt/v4/request"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/golang-jwt/jwt/v5/request"
|
||||
"xorm.io/xorm"
|
||||
|
||||
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||
@@ -63,47 +63,14 @@ func (s *TokenService) GetAllUnexpiredNormalTokensByUid(uid int64) ([]*models.To
|
||||
return tokenRecords, err
|
||||
}
|
||||
|
||||
// ParseToken returns the token model according to request data
|
||||
func (s *TokenService) ParseToken(c *core.Context) (*jwt.Token, *core.UserTokenClaims, error) {
|
||||
claims := &core.UserTokenClaims{}
|
||||
// ParseTokenByHeader returns the token model according to request data
|
||||
func (s *TokenService) ParseTokenByHeader(c *core.Context) (*jwt.Token, *core.UserTokenClaims, error) {
|
||||
return s.parseToken(c, request.BearerExtractor{})
|
||||
}
|
||||
|
||||
token, err := request.ParseFromRequest(c.Request, request.AuthorizationHeaderExtractor,
|
||||
func(token *jwt.Token) (interface{}, error) {
|
||||
uid, err := utils.StringToInt64(claims.Id)
|
||||
now := time.Now().Unix()
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[tokens.ParseToken] user \"uid:%s\" in token is invalid, because %s", claims.Id, err.Error())
|
||||
return nil, errs.ErrInvalidToken
|
||||
}
|
||||
|
||||
userTokenId, err := utils.StringToInt64(claims.UserTokenId)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[tokens.ParseToken] token \"utid:%s\" in token of user \"uid:%s\" is invalid, because %s", claims.UserTokenId, claims.Id, err.Error())
|
||||
return nil, errs.ErrInvalidUserTokenId
|
||||
}
|
||||
|
||||
tokenRecord, err := s.getTokenRecord(uid, userTokenId, claims.IssuedAt)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[tokens.ParseToken] token \"utid:%s\" of user \"uid:%s\" record not found, because %s", claims.UserTokenId, claims.Id, err.Error())
|
||||
return nil, errs.ErrTokenRecordNotFound
|
||||
}
|
||||
|
||||
if tokenRecord.ExpiredUnixTime < now {
|
||||
log.WarnfWithRequestId(c, "[tokens.ParseToken] token \"utid:%s\" of user \"uid:%s\" record is expired", claims.UserTokenId, claims.Id)
|
||||
return nil, errs.ErrTokenExpired
|
||||
}
|
||||
|
||||
return []byte(tokenRecord.Secret), nil
|
||||
}, request.WithClaims(claims))
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return token, claims, err
|
||||
// ParseTokenByArgument returns the token model according to request data
|
||||
func (s *TokenService) ParseTokenByArgument(c *core.Context, tokenParameterName string) (*jwt.Token, *core.UserTokenClaims, error) {
|
||||
return s.parseToken(c, request.ArgumentExtractor{tokenParameterName})
|
||||
}
|
||||
|
||||
// CreateToken generates a new normal token and saves to database
|
||||
@@ -163,19 +130,17 @@ func (s *TokenService) DeleteTokens(uid int64, tokenRecords []*models.TokenRecor
|
||||
|
||||
// DeleteTokenByClaims deletes given token from database
|
||||
func (s *TokenService) DeleteTokenByClaims(claims *core.UserTokenClaims) error {
|
||||
uid, err := utils.StringToInt64(claims.Id)
|
||||
|
||||
if err != nil {
|
||||
return errs.ErrUserIdInvalid
|
||||
}
|
||||
|
||||
userTokenId, err := utils.StringToInt64(claims.UserTokenId)
|
||||
|
||||
if err != nil {
|
||||
return errs.ErrInvalidUserTokenId
|
||||
}
|
||||
|
||||
return s.DeleteToken(&models.TokenRecord{Uid: uid, UserTokenId: userTokenId, CreatedUnixTime: claims.IssuedAt})
|
||||
return s.DeleteToken(&models.TokenRecord{
|
||||
Uid: claims.Uid,
|
||||
UserTokenId: userTokenId,
|
||||
CreatedUnixTime: claims.IssuedAt,
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteTokensBeforeTime deletes tokens that is created before specific tim
|
||||
@@ -230,6 +195,62 @@ func (s *TokenService) GenerateTokenId(tokenRecord *models.TokenRecord) string {
|
||||
return fmt.Sprintf("%d:%d:%d", tokenRecord.Uid, tokenRecord.CreatedUnixTime, tokenRecord.UserTokenId)
|
||||
}
|
||||
|
||||
func (s *TokenService) parseToken(c *core.Context, extractor request.Extractor) (*jwt.Token, *core.UserTokenClaims, error) {
|
||||
claims := &core.UserTokenClaims{}
|
||||
|
||||
token, err := request.ParseFromRequest(c.Request, extractor,
|
||||
func(token *jwt.Token) (interface{}, error) {
|
||||
now := time.Now().Unix()
|
||||
userTokenId, err := utils.StringToInt64(claims.UserTokenId)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[tokens.ParseToken] token \"utid:%s\" in token of user \"uid:%d\" is invalid, because %s", claims.UserTokenId, claims.Uid, err.Error())
|
||||
return nil, errs.ErrInvalidUserTokenId
|
||||
}
|
||||
|
||||
tokenRecord, err := s.getTokenRecord(claims.Uid, userTokenId, claims.IssuedAt)
|
||||
|
||||
if err != nil {
|
||||
log.WarnfWithRequestId(c, "[tokens.ParseToken] token \"utid:%s\" of user \"uid:%d\" record not found, because %s", claims.UserTokenId, claims.Uid, err.Error())
|
||||
return nil, errs.ErrTokenRecordNotFound
|
||||
}
|
||||
|
||||
if tokenRecord.ExpiredUnixTime < now {
|
||||
log.WarnfWithRequestId(c, "[tokens.ParseToken] token \"utid:%s\" of user \"uid:%d\" record is expired", claims.UserTokenId, claims.Uid)
|
||||
return nil, errs.ErrTokenExpired
|
||||
}
|
||||
|
||||
return []byte(tokenRecord.Secret), nil
|
||||
},
|
||||
request.WithClaims(claims),
|
||||
request.WithParser(jwt.NewParser(jwt.WithIssuedAt())),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if err == request.ErrNoTokenInRequest {
|
||||
return nil, nil, errs.ErrTokenIsEmpty
|
||||
}
|
||||
|
||||
if err == jwt.ErrTokenMalformed || err == jwt.ErrTokenUnverifiable || err == jwt.ErrTokenSignatureInvalid {
|
||||
log.WarnfWithRequestId(c, "[tokens.ParseToken] token is invalid, because %s", err.Error())
|
||||
return nil, nil, errs.ErrCurrentInvalidToken
|
||||
}
|
||||
|
||||
if err == jwt.ErrTokenExpired {
|
||||
return nil, nil, errs.ErrCurrentTokenExpired
|
||||
}
|
||||
|
||||
if err == jwt.ErrTokenUsedBeforeIssued {
|
||||
log.WarnfWithRequestId(c, "[tokens.ParseToken] token is invalid, because issue time is later than now")
|
||||
return nil, nil, errs.ErrCurrentInvalidToken
|
||||
}
|
||||
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return token, claims, err
|
||||
}
|
||||
|
||||
func (s *TokenService) createToken(user *models.User, tokenType core.TokenType, userAgent string, expiryDate time.Duration) (string, *core.UserTokenClaims, error) {
|
||||
var err error
|
||||
now := time.Now()
|
||||
@@ -249,13 +270,11 @@ func (s *TokenService) createToken(user *models.User, tokenType core.TokenType,
|
||||
|
||||
claims := &core.UserTokenClaims{
|
||||
UserTokenId: utils.Int64ToString(tokenRecord.UserTokenId),
|
||||
Uid: tokenRecord.Uid,
|
||||
Username: user.Username,
|
||||
Type: tokenRecord.TokenType,
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
Id: utils.Int64ToString(tokenRecord.Uid),
|
||||
IssuedAt: tokenRecord.CreatedUnixTime,
|
||||
ExpiresAt: tokenRecord.ExpiredUnixTime,
|
||||
},
|
||||
IssuedAt: tokenRecord.CreatedUnixTime,
|
||||
ExpiresAt: tokenRecord.ExpiredUnixTime,
|
||||
}
|
||||
|
||||
jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
|
||||
@@ -542,6 +542,14 @@ func (s *TransactionService) ModifyTransaction(transaction *models.Transaction,
|
||||
updateCols = append(updateCols, "comment")
|
||||
}
|
||||
|
||||
if transaction.GeoLongitude != oldTransaction.GeoLongitude {
|
||||
updateCols = append(updateCols, "geo_longitude")
|
||||
}
|
||||
|
||||
if transaction.GeoLatitude != oldTransaction.GeoLatitude {
|
||||
updateCols = append(updateCols, "geo_latitude")
|
||||
}
|
||||
|
||||
// Get and verify tags
|
||||
err = s.isTagsValid(sess, transaction, transactionTagIndexs, addTagIds)
|
||||
|
||||
@@ -955,6 +963,8 @@ func (s *TransactionService) GetRelatedTransferTransaction(originalTransaction *
|
||||
RelatedAccountId: originalTransaction.AccountId,
|
||||
RelatedAccountAmount: originalTransaction.Amount,
|
||||
Comment: originalTransaction.Comment,
|
||||
GeoLongitude: originalTransaction.GeoLongitude,
|
||||
GeoLatitude: originalTransaction.GeoLatitude,
|
||||
CreatedIp: originalTransaction.CreatedIp,
|
||||
CreatedUnixTime: originalTransaction.CreatedUnixTime,
|
||||
UpdatedUnixTime: originalTransaction.UpdatedUnixTime,
|
||||
@@ -1087,7 +1097,7 @@ func (s *TransactionService) GetAccountsAndCategoriesTotalIncomeAndExpense(uid i
|
||||
}
|
||||
|
||||
var transactionTotalAmounts []*models.Transaction
|
||||
err := s.UserDataDB(uid).Select("uid, category_id, account_id, SUM(amount) as amount").Where(condition, conditionParams...).GroupBy("category_id, account_id").Find(&transactionTotalAmounts)
|
||||
err := s.UserDataDB(uid).Select("category_id, account_id, SUM(amount) as amount").Where(condition, conditionParams...).GroupBy("category_id, account_id").Find(&transactionTotalAmounts)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
+27
-7
@@ -150,7 +150,7 @@ func (s *UserService) CreateUser(user *models.User) error {
|
||||
}
|
||||
|
||||
// UpdateUser saves an existed user model to database
|
||||
func (s *UserService) UpdateUser(user *models.User) (keyProfileUpdated bool, err error) {
|
||||
func (s *UserService) UpdateUser(user *models.User, modifyUserLanguage bool) (keyProfileUpdated bool, err error) {
|
||||
if user.Uid <= 0 {
|
||||
return false, errs.ErrUserIdInvalid
|
||||
}
|
||||
@@ -186,20 +186,40 @@ func (s *UserService) UpdateUser(user *models.User) (keyProfileUpdated bool, err
|
||||
updateCols = append(updateCols, "nickname")
|
||||
}
|
||||
|
||||
if user.DefaultCurrency != "" {
|
||||
updateCols = append(updateCols, "default_currency")
|
||||
}
|
||||
|
||||
if user.DefaultAccountId > 0 {
|
||||
updateCols = append(updateCols, "default_account_id")
|
||||
}
|
||||
|
||||
if models.TRANSACTION_EDIT_SCOPE_NONE <= user.TransactionEditScope && user.TransactionEditScope <= models.TRANSACTION_EDIT_SCOPE_THIS_YEAR_OR_LATER {
|
||||
updateCols = append(updateCols, "transaction_edit_scope")
|
||||
}
|
||||
|
||||
if modifyUserLanguage || user.Language != "" {
|
||||
updateCols = append(updateCols, "language")
|
||||
}
|
||||
|
||||
if user.DefaultCurrency != "" {
|
||||
updateCols = append(updateCols, "default_currency")
|
||||
}
|
||||
|
||||
if models.WEEKDAY_SUNDAY <= user.FirstDayOfWeek && user.FirstDayOfWeek <= models.WEEKDAY_SATURDAY {
|
||||
updateCols = append(updateCols, "first_day_of_week")
|
||||
}
|
||||
|
||||
if models.TRANSACTION_EDIT_SCOPE_NONE <= user.TransactionEditScope && user.TransactionEditScope <= models.TRANSACTION_EDIT_SCOPE_THIS_YEAR_OR_LATER {
|
||||
updateCols = append(updateCols, "transaction_edit_scope")
|
||||
if models.LONG_DATE_FORMAT_DEFAULT <= user.LongDateFormat && user.LongDateFormat <= models.LONG_DATE_FORMAT_D_M_YYYY {
|
||||
updateCols = append(updateCols, "long_date_format")
|
||||
}
|
||||
|
||||
if models.SHORT_DATE_FORMAT_DEFAULT <= user.ShortDateFormat && user.ShortDateFormat <= models.SHORT_DATE_FORMAT_D_M_YYYY {
|
||||
updateCols = append(updateCols, "short_date_format")
|
||||
}
|
||||
|
||||
if models.LONG_TIME_FORMAT_DEFAULT <= user.LongTimeFormat && user.LongTimeFormat <= models.LONG_TIME_FORMAT_HH_MM_SS_A {
|
||||
updateCols = append(updateCols, "long_time_format")
|
||||
}
|
||||
|
||||
if models.SHORT_TIME_FORMAT_DEFAULT <= user.ShortTimeFormat && user.ShortTimeFormat <= models.SHORT_TIME_FORMAT_HH_MM_A {
|
||||
updateCols = append(updateCols, "short_time_format")
|
||||
}
|
||||
|
||||
user.UpdatedUnixTime = now
|
||||
|
||||
+36
-5
@@ -62,13 +62,19 @@ const (
|
||||
InternalUuidGeneratorType string = "internal"
|
||||
)
|
||||
|
||||
// Map provider types
|
||||
const (
|
||||
OpenStreetMapProvider string = "openstreetmap"
|
||||
)
|
||||
|
||||
// Exchange rates data source types
|
||||
const (
|
||||
EuroCentralBankDataSource string = "euro_central_bank"
|
||||
BankOfCanadaDataSource string = "bank_of_canada"
|
||||
ReserveBankOfAustraliaDataSource string = "reserve_bank_of_australia"
|
||||
CzechNationalBankDataSource string = "czech_national_bank"
|
||||
NationalBankOfPolandDataSource string = "national_bank_of_poland"
|
||||
EuroCentralBankDataSource string = "euro_central_bank"
|
||||
BankOfCanadaDataSource string = "bank_of_canada"
|
||||
ReserveBankOfAustraliaDataSource string = "reserve_bank_of_australia"
|
||||
CzechNationalBankDataSource string = "czech_national_bank"
|
||||
NationalBankOfPolandDataSource string = "national_bank_of_poland"
|
||||
MonetaryAuthorityOfSingaporeDataSource string = "monetary_authority_of_singapore"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -168,6 +174,10 @@ type Config struct {
|
||||
// Data
|
||||
EnableDataExport bool
|
||||
|
||||
// Map
|
||||
MapProvider string
|
||||
EnableMapDataFetchProxy bool
|
||||
|
||||
// Exchange Rates
|
||||
ExchangeRatesDataSource string
|
||||
ExchangeRatesRequestTimeout uint32
|
||||
@@ -238,6 +248,12 @@ func LoadConfiguration(configFilePath string) (*Config, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = loadMapConfiguration(config, cfgFile, "map")
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = loadExchangeRatesConfiguration(config, cfgFile, "exchange_rates")
|
||||
|
||||
if err != nil {
|
||||
@@ -415,6 +431,19 @@ func loadDataConfiguration(config *Config, configFile *ini.File, sectionName str
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadMapConfiguration(config *Config, configFile *ini.File, sectionName string) error {
|
||||
if getConfigItemStringValue(configFile, sectionName, "map_provider") == "" {
|
||||
config.MapProvider = ""
|
||||
} else if getConfigItemStringValue(configFile, sectionName, "map_provider") == OpenStreetMapProvider {
|
||||
config.MapProvider = OpenStreetMapProvider
|
||||
} else {
|
||||
return errs.ErrInvalidMapProvider
|
||||
}
|
||||
|
||||
config.EnableMapDataFetchProxy = getConfigItemBoolValue(configFile, sectionName, "map_data_fetch_proxy", false)
|
||||
|
||||
return nil
|
||||
}
|
||||
func loadExchangeRatesConfiguration(config *Config, configFile *ini.File, sectionName string) error {
|
||||
if getConfigItemStringValue(configFile, sectionName, "data_source") == EuroCentralBankDataSource {
|
||||
config.ExchangeRatesDataSource = EuroCentralBankDataSource
|
||||
@@ -426,6 +455,8 @@ func loadExchangeRatesConfiguration(config *Config, configFile *ini.File, sectio
|
||||
config.ExchangeRatesDataSource = CzechNationalBankDataSource
|
||||
} else if getConfigItemStringValue(configFile, sectionName, "data_source") == NationalBankOfPolandDataSource {
|
||||
config.ExchangeRatesDataSource = NationalBankOfPolandDataSource
|
||||
} else if getConfigItemStringValue(configFile, sectionName, "data_source") == MonetaryAuthorityOfSingaporeDataSource {
|
||||
config.ExchangeRatesDataSource = MonetaryAuthorityOfSingaporeDataSource
|
||||
} else {
|
||||
return errs.ErrInvalidExchangeRatesDataSource
|
||||
}
|
||||
|
||||
@@ -3,8 +3,38 @@ package utils
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ListFileNamesWithPrefixAndSuffix returns file name list which has specified prefix and suffix
|
||||
func ListFileNamesWithPrefixAndSuffix(path string, prefix string, suffix string) []string {
|
||||
dir, err := os.Open(path)
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fileInfos, err := dir.Readdir(0)
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var fileNames []string
|
||||
|
||||
for i := 0; i < len(fileInfos); i++ {
|
||||
fileInfo := fileInfos[i]
|
||||
|
||||
if !fileInfo.IsDir() &&
|
||||
strings.HasPrefix(fileInfo.Name(), prefix) &&
|
||||
strings.HasSuffix(fileInfo.Name(), suffix) {
|
||||
fileNames = append(fileNames, fileInfo.Name())
|
||||
}
|
||||
}
|
||||
|
||||
return fileNames
|
||||
}
|
||||
|
||||
// IsExists returns whether specified file or directory path exits
|
||||
func IsExists(path string) (bool, error) {
|
||||
_, err := os.Stat(path)
|
||||
|
||||
@@ -35,8 +35,8 @@ type InternalUuidInfo struct {
|
||||
|
||||
// InternalUuidGenerator represents internal bundled uuid generator
|
||||
type InternalUuidGenerator struct {
|
||||
uuidServerId uint8
|
||||
uuidSeqNumbers [1 << internalUuidTypeBits]uint64
|
||||
uuidServerId uint8
|
||||
}
|
||||
|
||||
// NewInternalUuidGenerator returns a new internal uuid generator
|
||||
|
||||
@@ -78,22 +78,18 @@ func TestGenerateUuid_2000TimesIn2Seconds(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateUuid_1000000TimesConcurrent(t *testing.T) {
|
||||
concurrentCount := 50
|
||||
func TestGenerateUuid_10000TimesConcurrent(t *testing.T) {
|
||||
concurrentCount := 10
|
||||
generator, _ := NewInternalUuidGenerator(&settings.Config{UuidServerId: 3})
|
||||
var mutex sync.Mutex
|
||||
var generatedIds sync.Map
|
||||
var waitGroup sync.WaitGroup
|
||||
|
||||
for routineIndex := 0; routineIndex < concurrentCount; routineIndex++ {
|
||||
go func() {
|
||||
go func(currentRoutineIndex int) {
|
||||
waitGroup.Add(1)
|
||||
|
||||
for cycle := 0; cycle < 40000; cycle++ {
|
||||
if cycle%10000 == 0 { // each server can only generate 500,000 (50 * 10000) uuids in one second
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
}
|
||||
|
||||
for cycle := 0; cycle < 1000; cycle++ {
|
||||
expectedUnixTime := time.Now().Unix()
|
||||
uuid := generator.GenerateUuid(UUID_TYPE_USER)
|
||||
uuidInfo := generator.ParseUuidInfo(uuid)
|
||||
@@ -105,18 +101,18 @@ func TestGenerateUuid_1000000TimesConcurrent(t *testing.T) {
|
||||
}
|
||||
|
||||
if uuidInfo.SequentialId == 0 {
|
||||
if existedUnixTime, exists := generatedIds.Load(uuid); exists {
|
||||
if existedRoutineIndex, exists := generatedIds.Load(uuid); exists {
|
||||
mutex.Lock()
|
||||
assert.Fail(t, fmt.Sprintf("uuid \"%d\" conflicts, seq id is %d, existed unixtime is %d, current unix time is %d", uuid, uuidInfo.SequentialId, existedUnixTime, uuidInfo.UnixTime))
|
||||
assert.Fail(t, fmt.Sprintf("uuid \"%d\" conflicts, unix time is %d, seq id is %d, existed routine index is %d, current routine index is %d", uuid, uuidInfo.UnixTime, uuidInfo.SequentialId, existedRoutineIndex, currentRoutineIndex))
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
generatedIds.Store(uuid, uuidInfo.UnixTime)
|
||||
generatedIds.Store(uuid, currentRoutineIndex)
|
||||
}
|
||||
}
|
||||
|
||||
waitGroup.Done()
|
||||
}()
|
||||
}(routineIndex)
|
||||
}
|
||||
|
||||
waitGroup.Wait()
|
||||
@@ -199,23 +195,19 @@ func TestGenerateUuids_30TimesIn3Seconds(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateUuids_1000000TimesConcurrent(t *testing.T) {
|
||||
concurrentCount := 50
|
||||
expectedUuidCount := uint8(100)
|
||||
func TestGenerateUuids_20000TimesConcurrent(t *testing.T) {
|
||||
concurrentCount := 10
|
||||
expectedUuidCount := uint8(20)
|
||||
generator, _ := NewInternalUuidGenerator(&settings.Config{UuidServerId: 3})
|
||||
var mutex sync.Mutex
|
||||
var generatedIds sync.Map
|
||||
var waitGroup sync.WaitGroup
|
||||
|
||||
for routineIndex := 0; routineIndex < concurrentCount; routineIndex++ {
|
||||
go func() {
|
||||
go func(currentRoutineIndex int) {
|
||||
waitGroup.Add(1)
|
||||
|
||||
for cycle := 0; cycle < 400; cycle++ {
|
||||
if cycle%100 == 0 { // each server can only generate 500,000 (50 * 10000) uuids in one second
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
}
|
||||
|
||||
for cycle := 0; cycle < 100; cycle++ {
|
||||
expectedUnixTime := time.Now().Unix()
|
||||
uuids := generator.GenerateUuids(UUID_TYPE_USER, expectedUuidCount)
|
||||
|
||||
@@ -228,18 +220,18 @@ func TestGenerateUuids_1000000TimesConcurrent(t *testing.T) {
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
if existedUnixTime, exists := generatedIds.Load(uuids[i]); exists {
|
||||
if existedRoutineIndex, exists := generatedIds.Load(uuids[i]); exists {
|
||||
mutex.Lock()
|
||||
assert.Fail(t, fmt.Sprintf("uuid \"%d\" conflicts, seq id is %d, existed unixtime is %d, current unix time is %d", uuids[i], uuidInfo.SequentialId, existedUnixTime, uuidInfo.UnixTime))
|
||||
assert.Fail(t, fmt.Sprintf("uuid \"%d\" conflicts, unix time is %d, seq id is %d, existed routine index is %d, current routine index is %d", uuids[i], uuidInfo.UnixTime, uuidInfo.SequentialId, existedRoutineIndex, currentRoutineIndex))
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
generatedIds.Store(uuids[i], uuidInfo.UnixTime)
|
||||
generatedIds.Store(uuids[i], currentRoutineIndex)
|
||||
}
|
||||
}
|
||||
|
||||
waitGroup.Done()
|
||||
}()
|
||||
}(routineIndex)
|
||||
}
|
||||
|
||||
waitGroup.Wait()
|
||||
|
||||
@@ -132,7 +132,8 @@ var AllCurrencyNames = map[string]bool{
|
||||
"SEK": true, //Swedish Krona
|
||||
"SGD": true, //Singapore Dollar
|
||||
"SHP": true, //Saint Helena Pound
|
||||
"SLL": true, //Leone
|
||||
"SLE": true, //Leone (new leone)
|
||||
"SLL": true, //Leone (old leone)
|
||||
"SOS": true, //Somali Shilling
|
||||
"SRD": true, //Surinam Dollar
|
||||
"SSP": true, //South Sudanese Pound
|
||||
@@ -154,6 +155,8 @@ var AllCurrencyNames = map[string]bool{
|
||||
"USD": true, //US Dollar
|
||||
"UYU": true, //Peso Uruguayo
|
||||
"UZS": true, //Uzbekistan Sum
|
||||
"VED": true, //Bolívar Digital
|
||||
"VEF": true, //Bolívar Fuerte
|
||||
"VES": true, //Bolívar Soberano
|
||||
"VND": true, //Dong
|
||||
"VUV": true, //Vatu
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
'postcss-preset-env': {},
|
||||
},
|
||||
};
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 936 B |
Binary file not shown.
|
After Width: | Height: | Size: 736 B |
-346
@@ -1,346 +0,0 @@
|
||||
<template>
|
||||
<f7-app :params="f7params">
|
||||
<f7-view id="main-view" class="safe-areas" main url="/"></f7-view>
|
||||
</f7-app>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import routes from './router/mobile.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
const self = this;
|
||||
|
||||
return {
|
||||
f7params: {
|
||||
name: 'ezBookkeeping',
|
||||
id: 'net.mayswind.ezbookkeeping',
|
||||
theme: 'ios',
|
||||
autoDarkTheme: self.$settings.isEnableAutoDarkMode(),
|
||||
routes: routes,
|
||||
actions: {
|
||||
animate: self.$settings.isEnableAnimate(),
|
||||
backdrop: true,
|
||||
closeOnEscape: true
|
||||
},
|
||||
dialog: {
|
||||
animate: self.$settings.isEnableAnimate(),
|
||||
backdrop: true
|
||||
},
|
||||
popover: {
|
||||
animate: self.$settings.isEnableAnimate(),
|
||||
backdrop: true,
|
||||
closeOnEscape: true
|
||||
},
|
||||
popup: {
|
||||
animate: self.$settings.isEnableAnimate(),
|
||||
backdrop: true,
|
||||
closeOnEscape: true,
|
||||
swipeToClose: true
|
||||
},
|
||||
sheet: {
|
||||
animate: self.$settings.isEnableAnimate(),
|
||||
backdrop: true,
|
||||
closeOnEscape: true
|
||||
},
|
||||
smartSelect: {
|
||||
routableModals: false
|
||||
},
|
||||
touch: {
|
||||
tapHold: true,
|
||||
disableContextMenu: true
|
||||
},
|
||||
view: {
|
||||
animate: self.$settings.isEnableAnimate(),
|
||||
pushState: !self.isiOSHomeScreenMode(),
|
||||
pushStateAnimate: false,
|
||||
iosSwipeBackAnimateShadow: false,
|
||||
mdSwipeBackAnimateShadow: false
|
||||
},
|
||||
calendar: {
|
||||
locale: 'en',
|
||||
openIn: 'customModal',
|
||||
backdrop: true
|
||||
},
|
||||
serviceWorker: {
|
||||
path: self.$settings.isProduction() ? './sw.js' : undefined,
|
||||
scope: './',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.$user.isUserLogined()) {
|
||||
if (!this.$settings.isEnableApplicationLock()) {
|
||||
// refresh token if user is logined
|
||||
this.$store.dispatch('refreshTokenAndRevokeOldToken');
|
||||
|
||||
// auto refresh exchange rates data
|
||||
if (this.$settings.isAutoUpdateExchangeRatesData()) {
|
||||
this.$store.dispatch('getLatestExchangeRates', { silent: true, force: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isiOSHomeScreenMode() {
|
||||
if ((/iphone|ipod|ipad/gi).test(navigator.platform) && (/Safari/i).test(navigator.appVersion) &&
|
||||
window.matchMedia && window.matchMedia('(display-mode: standalone)').matches
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/** Global style **/
|
||||
html, body {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
body {
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/** Common class **/
|
||||
.no-right-border {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.no-bottom-border {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.work-break-all {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.full-line {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.smaller {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.readonly {
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
.segmented.readonly .button:not(.button-active) > span,
|
||||
.list.readonly .item-content .item-title.item-label,
|
||||
.list.readonly .item-content .item-title > .item-header {
|
||||
opacity: 0.55 !important;
|
||||
}
|
||||
|
||||
/** Replacing the default style of framework7 **/
|
||||
:root {
|
||||
--f7-theme-color: #c67e48;
|
||||
--f7-theme-color-rgb: 198, 126, 72;
|
||||
--f7-theme-color-shade: #af6a36;
|
||||
--f7-theme-color-tint: #d09467;
|
||||
|
||||
--default-icon-color: var(--f7-text-color);
|
||||
}
|
||||
|
||||
:root .theme-dark {
|
||||
--default-icon-color: var(--f7-text-color);
|
||||
}
|
||||
|
||||
.ios .theme-dark, .ios.theme-dark {
|
||||
--f7-list-item-header-text-color: inherit !important;
|
||||
}
|
||||
|
||||
i.icon.la, i.icon.las, i.icon.lab {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.chip.chip-placeholder {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/** Common class for replacing the default style of framework7 **/
|
||||
.navbar .navbar-compact-icons.right a + a {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.toolbar-item-auto-size .toolbar-inner {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
.toolbar-item-auto-size .toolbar-inner > .link {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.tabbar-primary-link,
|
||||
.tabbar-item-changed {
|
||||
color: var(--f7-theme-color);
|
||||
}
|
||||
|
||||
.tabbar-text-with-ellipsis > span {
|
||||
display: block;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.list-item-media-valign-middle .item-media {
|
||||
align-self: normal !important;
|
||||
}
|
||||
|
||||
.list-item-no-item-after .item-after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.list-item-with-header-and-title .item-content .item-header {
|
||||
margin-bottom: 11px;
|
||||
}
|
||||
|
||||
.list-item-with-header-and-title.list-item-title-hide-overflow .item-content .list-item-custom-title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.list .item-content .input.list-title-input {
|
||||
margin-top: calc(-1 * var(--f7-list-item-padding-vertical));
|
||||
margin-bottom: calc(-1 * var(--f7-list-item-padding-vertical));
|
||||
}
|
||||
|
||||
.list .item-content .list-item-valign-middle {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.list .item-content .list-item-showing {
|
||||
color: rgba(0, 0, 0, 0.2);
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.theme-dark .list .item-content .list-item-showing {
|
||||
color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.accordion-item.list-item-checked > .item-link > .item-content .item-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.list .item-content .list-item-checked-icon {
|
||||
font-size: 20px;
|
||||
color: var(--f7-radio-active-color, var(--f7-theme-color));
|
||||
}
|
||||
|
||||
.ebk-list-item-error-info div.item-footer {
|
||||
color: var(--f7-input-error-text-color)
|
||||
}
|
||||
|
||||
.skeleton-text .list-item-toggle .item-after {
|
||||
height: var(--f7-toggle-height);
|
||||
}
|
||||
|
||||
.skeleton-text .list-item-toggle .item-after > span {
|
||||
line-height: var(--f7-toggle-height);
|
||||
font-size: var(--f7-toggle-height);
|
||||
}
|
||||
|
||||
.no-sortable > .sortable-handler {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.card-header-content {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.card-chevron-icon {
|
||||
color: var(--f7-list-chevron-icon-color);
|
||||
font-size: var(--f7-list-chevron-icon-font-size);
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.icon-after-text {
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.badge.right-bottom-icon {
|
||||
margin-left: -12px;
|
||||
margin-top: 14px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.badge.right-bottom-icon > .icon {
|
||||
font-size: 13px;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
/** Nested List item for framework7 **/
|
||||
.nested-list-item .item-title {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nested-list-item .item-inner {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item .nested-list-item-child .item-inner {
|
||||
padding-bottom: var(--f7-list-item-padding-vertical);
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item .item-link.active-state {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item .item-link .item-inner {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item .item-link .item-inner:before {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item .item-link.active-state .item-inner .nested-list-item-child .item-link.active-state {
|
||||
background-color: var(--f7-list-link-pressed-bg-color);
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item .item-link .item-inner .nested-list-item-child .item-link .item-inner {
|
||||
padding-right: calc(var(--f7-list-chevron-icon-area) + var(--f7-list-item-padding-horizontal) + var(--f7-safe-area-right));
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item .item-link .item-inner .nested-list-item-child .item-link .item-inner:before {
|
||||
color: var(--f7-list-chevron-icon-color);
|
||||
}
|
||||
|
||||
.nested-list-item .nested-list-item-title {
|
||||
align-self: center;
|
||||
margin-left: var(--f7-list-item-media-margin);
|
||||
margin-right: var(--f7-list-item-media-margin);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.sortable-enabled .nested-list-item .nested-list-item-child .item-inner {
|
||||
padding-right: var(--f7-safe-area-right) !important;
|
||||
}
|
||||
|
||||
/** Replacing the default style of Vue-pincode-input **/
|
||||
.vue-pincode-input {
|
||||
margin: 3px !important;
|
||||
padding: 5px !important;
|
||||
box-shadow: 0 0 2px rgba(0,0,0,.5) !important;
|
||||
}
|
||||
|
||||
.theme-dark .vue-pincode-input {
|
||||
box-shadow: 0 0 2px rgba(255,255,255,.5) !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,684 @@
|
||||
<template>
|
||||
<img style="display: none;" :src="devCookiePath" v-if="!isProduction" />
|
||||
<f7-app v-bind="f7params">
|
||||
<f7-view id="main-view" class="safe-areas" main url="/"></f7-view>
|
||||
</f7-app>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { f7ready } from 'framework7-vue';
|
||||
import routes from './router/mobile.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
const self = this;
|
||||
let darkMode = 'auto';
|
||||
|
||||
if (self.$settings.getTheme() === 'light') {
|
||||
darkMode = false;
|
||||
} else if (self.$settings.getTheme() === 'dark') {
|
||||
darkMode = true;
|
||||
}
|
||||
|
||||
return {
|
||||
isProduction: self.$settings.isProduction(),
|
||||
devCookiePath: self.$settings.isProduction() ? '' : '/dev/cookies',
|
||||
f7params: {
|
||||
name: 'ezBookkeeping',
|
||||
theme: 'ios',
|
||||
colors: {
|
||||
primary: '#c67e48'
|
||||
},
|
||||
routes: routes,
|
||||
darkMode: darkMode,
|
||||
touch: {
|
||||
disableContextMenu: true,
|
||||
tapHold: true
|
||||
},
|
||||
serviceWorker: {
|
||||
path: self.$settings.isProduction() ? './sw.js' : undefined,
|
||||
scope: './',
|
||||
},
|
||||
actions: {
|
||||
animate: self.$settings.isEnableAnimate(),
|
||||
backdrop: true,
|
||||
closeOnEscape: true
|
||||
},
|
||||
dialog: {
|
||||
animate: self.$settings.isEnableAnimate(),
|
||||
backdrop: true
|
||||
},
|
||||
popover: {
|
||||
animate: self.$settings.isEnableAnimate(),
|
||||
backdrop: true,
|
||||
closeOnEscape: true
|
||||
},
|
||||
popup: {
|
||||
animate: self.$settings.isEnableAnimate(),
|
||||
backdrop: true,
|
||||
closeOnEscape: true,
|
||||
swipeToClose: true
|
||||
},
|
||||
sheet: {
|
||||
animate: self.$settings.isEnableAnimate(),
|
||||
backdrop: true,
|
||||
closeOnEscape: true
|
||||
},
|
||||
smartSelect: {
|
||||
routableModals: false
|
||||
},
|
||||
view: {
|
||||
animate: self.$settings.isEnableAnimate(),
|
||||
browserHistory: !self.isiOSHomeScreenMode(),
|
||||
browserHistoryInitialMatch: true,
|
||||
browserHistoryAnimate: false,
|
||||
iosSwipeBackAnimateShadow: false,
|
||||
mdSwipeBackAnimateShadow: false
|
||||
}
|
||||
},
|
||||
isDarkMode: undefined,
|
||||
hasPushPopupBackdrop: undefined,
|
||||
hasBackdrop: undefined
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const self = this;
|
||||
|
||||
if (self.$user.isUserLogined()) {
|
||||
if (!self.$settings.isEnableApplicationLock()) {
|
||||
// refresh token if user is logined
|
||||
self.$store.dispatch('refreshTokenAndRevokeOldToken').then(response => {
|
||||
if (response.user && response.user.language) {
|
||||
self.$locale.setLanguage(response.user.language);
|
||||
}
|
||||
});
|
||||
|
||||
// auto refresh exchange rates data
|
||||
if (self.$settings.isAutoUpdateExchangeRatesData()) {
|
||||
self.$store.dispatch('getLatestExchangeRates', { silent: true, force: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
f7ready((f7) => {
|
||||
this.isDarkMode = f7.darkMode;
|
||||
this.setThemeColorMeta(f7.darkMode);
|
||||
|
||||
f7.on('actionsOpen', (event) => this.onBackdropChanged(event));
|
||||
f7.on('actionsClose', (event) => this.onBackdropChanged(event));
|
||||
f7.on('dialogOpen', (event) => this.onBackdropChanged(event));
|
||||
f7.on('dialogClose', (event) => this.onBackdropChanged(event));
|
||||
f7.on('popoverOpen', (event) => this.onBackdropChanged(event));
|
||||
f7.on('popoverClose', (event) => this.onBackdropChanged(event));
|
||||
f7.on('popupOpen', (event) => this.onBackdropChanged(event));
|
||||
f7.on('popupClose', (event) => this.onBackdropChanged(event));
|
||||
f7.on('sheetOpen', (event) => this.onBackdropChanged(event));
|
||||
f7.on('sheetClose', (event) => this.onBackdropChanged(event));
|
||||
|
||||
f7.on('pageBeforeOut', () => {
|
||||
if (this.$ui.isModalShowing()) {
|
||||
f7.actions.close('.actions-modal.modal-in', false);
|
||||
f7.dialog.close('.dialog.modal-in', false);
|
||||
f7.popover.close('.popover.modal-in', false);
|
||||
f7.popup.close('.popup.modal-in', false);
|
||||
f7.sheet.close('.sheet-modal.modal-in', false);
|
||||
}
|
||||
});
|
||||
|
||||
f7.on('darkModeChange', (isDarkMode) => {
|
||||
this.isDarkMode = isDarkMode;
|
||||
this.setThemeColorMeta(isDarkMode);
|
||||
});
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
isiOSHomeScreenMode() {
|
||||
if ((/iphone|ipod|ipad/gi).test(navigator.platform) && (/Safari/i).test(navigator.appVersion) &&
|
||||
window.matchMedia && window.matchMedia('(display-mode: standalone)').matches
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
onBackdropChanged(event) {
|
||||
if (event.push) {
|
||||
this.hasPushPopupBackdrop = event.opened;
|
||||
} else {
|
||||
this.hasBackdrop = event.opened;
|
||||
}
|
||||
|
||||
this.setThemeColorMeta(this.isDarkMode);
|
||||
},
|
||||
setThemeColorMeta(isDarkMode) {
|
||||
if (this.hasPushPopupBackdrop) {
|
||||
document.querySelector('meta[name=theme-color]').setAttribute('content', '#000');
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDarkMode) {
|
||||
if (this.hasBackdrop) {
|
||||
document.querySelector('meta[name=theme-color]').setAttribute('content', '#0b0b0b');
|
||||
} else {
|
||||
document.querySelector('meta[name=theme-color]').setAttribute('content', '#121212');
|
||||
}
|
||||
} else {
|
||||
if (this.hasBackdrop) {
|
||||
document.querySelector('meta[name=theme-color]').setAttribute('content', '#949495');
|
||||
} else {
|
||||
document.querySelector('meta[name=theme-color]').setAttribute('content', '#f6f6f8');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/** Global style **/
|
||||
html, body {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
body {
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/** Common class **/
|
||||
.no-right-border {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.no-bottom-border {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.work-break-all {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.full-line {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.smaller {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.readonly {
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
.skeleton-text {
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
.segmented.readonly .button:not(.button-active) > span,
|
||||
.list.readonly .item-content .item-title.item-label,
|
||||
.list.readonly .item-content .item-title > .item-header {
|
||||
opacity: 0.55 !important;
|
||||
}
|
||||
|
||||
/** Replacing the default style of framework7 **/
|
||||
:root {
|
||||
--f7-popup-push-offset: 5px;
|
||||
--f7-color-gray: #8e8e93;
|
||||
--f7-color-gray-rgb: 142, 142, 147;
|
||||
--f7-color-gray-shade: #79797f;
|
||||
--f7-color-gray-tint: #a3a3a7;
|
||||
--default-icon-color: var(--f7-text-color);
|
||||
}
|
||||
|
||||
:root .dark {
|
||||
--default-icon-color: var(--f7-text-color);
|
||||
}
|
||||
|
||||
.color-gray {
|
||||
--f7-theme-color: var(--f7-color-gray);
|
||||
--f7-theme-color-rgb: var(--f7-color-gray-rgb);
|
||||
--f7-theme-color-shade: var(--f7-color-gray-shade);
|
||||
--f7-theme-color-tint: var(--f7-color-gray-tint);
|
||||
}
|
||||
|
||||
.ios .dark, .ios.dark {
|
||||
--f7-list-item-header-text-color: inherit !important;
|
||||
}
|
||||
|
||||
i.icon.la, i.icon.las, i.icon.lab {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.chip.chip-placeholder {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/** Replacing the default style of @vuepic/vue-datepicker **/
|
||||
.dp__theme_light {
|
||||
--dp-primary-color: #c67e48;
|
||||
}
|
||||
|
||||
.dp__theme_dark {
|
||||
--dp-primary-color: #c67e48;
|
||||
}
|
||||
|
||||
/** Common class for replacing the default style of framework7 **/
|
||||
.navbar .navbar-compact-icons.right a + a {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.toolbar-item-auto-size .toolbar-inner {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
.toolbar-item-auto-size .toolbar-inner > .link {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.tabbar-primary-link,
|
||||
.tabbar-item-changed {
|
||||
color: var(--f7-theme-color);
|
||||
}
|
||||
|
||||
.tabbar-text-with-ellipsis > span {
|
||||
display: block;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.block-title .accordion-item-toggle .icon {
|
||||
color: var(--f7-list-chevron-icon-color);
|
||||
font-size: var(--f7-list-chevron-icon-font-size);
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.list-item-media-valign-middle .item-media {
|
||||
align-self: normal !important;
|
||||
}
|
||||
|
||||
.list-item-no-item-after .item-after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.list-item-with-header-and-title .item-content .item-header {
|
||||
margin-bottom: 11px;
|
||||
}
|
||||
|
||||
.list-item-with-header-and-title.list-item-title-hide-overflow .item-content .list-item-custom-title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.list .item-content .input.list-title-input {
|
||||
margin-top: calc(-1 * var(--f7-list-item-padding-vertical));
|
||||
margin-bottom: calc(-1 * var(--f7-list-item-padding-vertical));
|
||||
}
|
||||
|
||||
.list .item-content .list-item-valign-middle {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.list .item-content .list-item-showing {
|
||||
color: rgba(0, 0, 0, 0.2);
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dark .list .item-content .list-item-showing {
|
||||
color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.accordion-item.list-item-checked > .item-link > .item-content .item-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.list.list-dividers li.has-child-list-item .item-inner:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
background-color: var(--f7-list-item-border-color);
|
||||
display: block !important;
|
||||
z-index: 15;
|
||||
top: auto;
|
||||
right: auto;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
transform-origin: 50% 100%;
|
||||
transform: scaleY(calc(1 / var(--f7-device-pixel-ratio)));
|
||||
}
|
||||
|
||||
.list.list-dividers li.list-group-title:first-child,
|
||||
.list.list-dividers li.list-group-title.actual-first-child {
|
||||
border-radius: var(--f7-list-inset-border-radius) var(--f7-list-inset-border-radius) 0 0;
|
||||
}
|
||||
|
||||
.list.list-dividers li.list-group-title:first-child:before,
|
||||
.list.list-dividers li.list-group-title.actual-first-child:before {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.list.list-dividers li:last-child > .swipeout-content > .item-content > .item-inner:after,
|
||||
.list.list-dividers li:last-child > .swipeout-content > .item-link > .item-content > .item-inner:after,
|
||||
.list.list-dividers li.actual-last-child > .swipeout-content > .item-content > .item-inner:after,
|
||||
.list.list-dividers li.actual-last-child > .swipeout-content > .item-link > .item-content > .item-inner:after {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.list.inset li.list-group-title:first-child > a.button {
|
||||
border-radius: var(--f7-button-border-radius);
|
||||
}
|
||||
|
||||
.list.inset li.swipeout.actual-first-child,
|
||||
.list.inset li.actual-first-child > a {
|
||||
border-radius: var(--f7-list-inset-border-radius) var(--f7-list-inset-border-radius) 0 0;
|
||||
}
|
||||
|
||||
.list.inset li.swipeout.actual-last-child,
|
||||
.list.inset li.actual-last-child > a {
|
||||
border-radius: 0 0 var(--f7-list-inset-border-radius) var(--f7-list-inset-border-radius);
|
||||
}
|
||||
|
||||
.list.inset li.swipeout.actual-first-child.actual-last-child,
|
||||
.list.inset li.actual-first-child.actual-last-child > a {
|
||||
border-radius: var(--f7-list-inset-border-radius);
|
||||
}
|
||||
|
||||
.list.inset.list-has-group-title li.swipeout.actual-first-child,
|
||||
.list.inset.list-has-group-title li.actual-first-child > a {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.list.inset.list-has-group-title li.swipeout:first-child:last-child,
|
||||
.list.inset.list-has-group-title li:first-child:last-child > a,
|
||||
.list.inset.list-has-group-title li.swipeout.actual-first-child.actual-last-child,
|
||||
.list.inset.list-has-group-title li.actual-first-child.actual-last-child > a {
|
||||
border-radius: 0 0 var(--f7-list-inset-border-radius) var(--f7-list-inset-border-radius);
|
||||
}
|
||||
|
||||
.accordion-item .block-title+.accordion-item-content .list.inset li.swipeout:first-child:not(:last-child),
|
||||
.accordion-item .block-title+.accordion-item-content .list.inset li:first-child:not(:last-child) > a {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.accordion-item .block-title+.accordion-item-content .list.inset li.swipeout:first-child:last-child,
|
||||
.accordion-item .block-title+.accordion-item-content .list.inset li:first-child:last-child > a,
|
||||
.accordion-item .block-title+.accordion-item-content .list.inset li.swipeout.actual-last-child:first-child,
|
||||
.accordion-item .block-title+.accordion-item-content .list.inset li.actual-last-child:first-child > a {
|
||||
border-radius: 0 0 var(--f7-list-inset-border-radius) var(--f7-list-inset-border-radius);
|
||||
}
|
||||
|
||||
.list .item-content .list-item-checked-icon {
|
||||
font-size: 20px;
|
||||
color: var(--f7-radio-active-color, var(--f7-theme-color));
|
||||
margin-right: calc(var(--f7-list-item-media-margin) + var(--f7-checkbox-extra-margin));
|
||||
}
|
||||
|
||||
.list .item-content > .item-inner > .item-after .list-item-checked-icon {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.list li.no-margin .item-content.item-input {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ebk-list-item-error-info div.item-footer {
|
||||
color: var(--f7-input-error-text-color)
|
||||
}
|
||||
|
||||
.skeleton-text .list-item-toggle .item-after {
|
||||
height: var(--f7-toggle-height);
|
||||
}
|
||||
|
||||
.skeleton-text .list-item-toggle .item-after > span {
|
||||
line-height: var(--f7-toggle-height);
|
||||
font-size: var(--f7-toggle-height);
|
||||
}
|
||||
|
||||
.no-sortable > .sortable-handler {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.card-header-content {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.icon-after-text {
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.badge.right-bottom-icon {
|
||||
margin-left: -12px;
|
||||
margin-top: 14px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.badge.right-bottom-icon > .icon {
|
||||
font-size: 13px;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
/** Swipe handler **/
|
||||
.swipe-handler {
|
||||
height: 26px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.swipe-handler:after {
|
||||
content: "";
|
||||
width: 36px;
|
||||
height: 6px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
margin-left: -18px;
|
||||
margin-top: -8px;
|
||||
border-radius: 3px;
|
||||
background: #666
|
||||
}
|
||||
|
||||
/** list-item-with-multi-item for framework7 **/
|
||||
.list-item-with-multi-item .item-content,
|
||||
.list-item-with-multi-item .item-inner {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.list-item-with-multi-item .item-inner > div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.list-item-with-multi-item > .item-content > .item-inner:after {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.list-item-with-multi-item .list-item-subitem:first-child .item-content {
|
||||
padding-left: calc(var(--f7-list-item-padding-horizontal) + var(--f7-safe-area-left));
|
||||
}
|
||||
|
||||
.list-item-with-multi-item .list-item-subitem .item-inner {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding-left: calc(var(--f7-list-item-padding-horizontal) + var(--f7-safe-area-left));
|
||||
padding-top: var(--f7-list-item-padding-vertical);
|
||||
padding-bottom: var(--f7-list-item-padding-vertical);
|
||||
}
|
||||
|
||||
.list-item-with-multi-item .list-item-subitem:first-child .item-inner {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
/** Combination list for framework7 **/
|
||||
.combination-list-wrapper {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.combination-list-wrapper .block-title {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.combination-list-wrapper .list.combination-list-header {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.combination-list-wrapper .list.combination-list-header .item-title {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.combination-list-wrapper .list.combination-list-header > ul {
|
||||
background-color: var(--f7-list-group-title-bg-color);
|
||||
}
|
||||
|
||||
.combination-list-wrapper .list.combination-list-header.combination-list-opened > ul {
|
||||
border-radius: var(--f7-list-inset-border-radius) var(--f7-list-inset-border-radius) 0 0;
|
||||
}
|
||||
|
||||
.combination-list-wrapper .list.combination-list-header.combination-list-closed > ul {
|
||||
border-radius: var(--f7-list-inset-border-radius);
|
||||
}
|
||||
|
||||
.combination-list-wrapper .list.combination-list-header .combination-list-chevron-icon {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.combination-list-wrapper .list.combination-list-content.inset > ul {
|
||||
border-radius: 0 0 var(--f7-list-inset-border-radius) var(--f7-list-inset-border-radius);
|
||||
}
|
||||
|
||||
/** Nested List item for framework7 **/
|
||||
.nested-list-item .item-title {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item > .swipeout-content > .item-content > .item-inner,
|
||||
.nested-list-item.has-child-list-item > .swipeout-content > .item-link > .item-content > .item-inner {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item .nested-list-item-child .item-inner {
|
||||
padding-bottom: var(--f7-list-item-padding-vertical);
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item .item-link.active-state {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item .item-link .item-inner {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item .item-link .item-inner:before {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item .item-link.active-state .item-inner .nested-list-item-child .item-link.active-state {
|
||||
background-color: var(--f7-list-link-pressed-bg-color);
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item .item-link .item-inner .nested-list-item-child .item-link .item-inner {
|
||||
padding-right: calc(var(--f7-list-chevron-icon-area) + var(--f7-list-item-padding-horizontal) + var(--f7-safe-area-right));
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item .item-link .item-inner .nested-list-item-child .item-link .item-inner:before {
|
||||
color: var(--f7-list-chevron-icon-color);
|
||||
}
|
||||
|
||||
.nested-list-item .nested-list-item-title {
|
||||
align-self: center;
|
||||
margin-right: var(--f7-list-item-media-margin);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item .nested-list-item-title {
|
||||
margin-left: var(--f7-list-item-media-margin);
|
||||
}
|
||||
|
||||
.nested-list-item.has-child-list-item > .swipeout-content > .item-content > .item-inner:after,
|
||||
.nested-list-item.has-child-list-item > .swipeout-content > .item-link > .item-content > .item-inner:after {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.list.inset li.nested-list-item.has-child-list-item .item-inner li.nested-list-item-child,
|
||||
.list.inset li.nested-list-item.has-child-list-item .item-inner li.nested-list-item-child > a {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.list.list-dividers li.nested-list-item.has-child-list-item .item-inner .nested-list-item-child .item-inner:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
background-color: var(--f7-list-item-border-color);
|
||||
display: block !important;
|
||||
z-index: 15;
|
||||
top: auto;
|
||||
right: auto;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
transform-origin: 50% 100%;
|
||||
transform: scaleY(calc(1 / var(--f7-device-pixel-ratio)));
|
||||
}
|
||||
|
||||
.list.list-dividers li.nested-list-item.has-child-list-item:last-child .item-inner .nested-list-item-child:last-child .item-inner:after,
|
||||
.list.list-dividers li.nested-list-item.has-child-list-item.actual-last-child .item-inner .nested-list-item-child.actual-last-child .item-inner:after {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.sortable-enabled .nested-list-item .nested-list-item-child .item-inner {
|
||||
padding-right: var(--f7-safe-area-right) !important;
|
||||
}
|
||||
|
||||
/** Fix @vuepic/vue-datepicker style issue **/
|
||||
.dp__main.dp__flex_display {
|
||||
flex-direction: column
|
||||
}
|
||||
|
||||
.dp__main .dp__preset_range {
|
||||
white-space: inherit;
|
||||
}
|
||||
|
||||
.dp__main .dp__menu_inner {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.dp__main .dp__menu_inner .dp__month_year_row > button {
|
||||
width: inherit;
|
||||
}
|
||||
|
||||
.dp__main .dp__menu_inner .dp__month_year_row > button.dp__button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dp__main .dp__menu_inner .dp__month_year_row .dp__month_year_wrap > button {
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.dp__main .dp__calendar .dp__calendar_item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.dp__main .dp__calendar .dp__calendar_item > .dp__cell_inner {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,27 +1,27 @@
|
||||
<template>
|
||||
<f7-sheet :opened="show" @sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
|
||||
<f7-sheet swipe-to-close swipe-handler=".swipe-handler"
|
||||
:opened="show"
|
||||
@sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
|
||||
<f7-toolbar>
|
||||
<div class="swipe-handler"></div>
|
||||
<div class="left"></div>
|
||||
<div class="right">
|
||||
<f7-link sheet-close :text="$t('Done')"></f7-link>
|
||||
</div>
|
||||
</f7-toolbar>
|
||||
<f7-page-content>
|
||||
<f7-block class="margin-vertical">
|
||||
<f7-row class="padding-vertical padding-horizontal-half"
|
||||
:class="{ 'row-has-selected-item': hasSelectedIcon(row) }"
|
||||
v-for="(row, idx) in allColorRows" :key="idx">
|
||||
<f7-col class="text-align-center" v-for="colorInfo in row" :key="colorInfo.color">
|
||||
<f7-icon f7="app_fill"
|
||||
:style="colorInfo.color | iconStyle('default', 'var(--default-icon-color)')"
|
||||
@click.native="onColorClicked(colorInfo)">
|
||||
<f7-block class="margin-vertical no-padding">
|
||||
<div class="grid grid-cols-7 padding-vertical-half padding-horizontal-half"
|
||||
:class="{ 'row-has-selected-item': hasSelectedIcon(row) }"
|
||||
:key="idx" v-for="(row, idx) in allColorRows">
|
||||
<div class="text-align-center" :key="colorInfo.color" v-for="colorInfo in row">
|
||||
<ItemIcon icon-type="fixed-f7" icon-id="app_fill" :color="colorInfo.color" @click="onColorClicked(colorInfo)">
|
||||
<f7-badge color="default" class="right-bottom-icon" v-if="currentValue && currentValue === colorInfo.color">
|
||||
<f7-icon f7="checkmark_alt"></f7-icon>
|
||||
</f7-badge>
|
||||
</f7-icon>
|
||||
</f7-col>
|
||||
<f7-col v-for="idx in (itemPerRow - row.length)" :key="idx"></f7-col>
|
||||
</f7-row>
|
||||
</ItemIcon>
|
||||
</div>
|
||||
</div>
|
||||
</f7-block>
|
||||
</f7-page-content>
|
||||
</f7-sheet>
|
||||
@@ -30,16 +30,20 @@
|
||||
<script>
|
||||
export default {
|
||||
props: [
|
||||
'value',
|
||||
'modelValue',
|
||||
'columnCount',
|
||||
'show',
|
||||
'allColorInfos'
|
||||
],
|
||||
emits: [
|
||||
'update:modelValue',
|
||||
'update:show'
|
||||
],
|
||||
data() {
|
||||
const self = this;
|
||||
|
||||
return {
|
||||
currentValue: self.value,
|
||||
currentValue: self.modelValue,
|
||||
itemPerRow: self.columnCount || 7
|
||||
}
|
||||
},
|
||||
@@ -64,11 +68,11 @@ export default {
|
||||
methods: {
|
||||
onColorClicked(colorInfo) {
|
||||
this.currentValue = colorInfo.color;
|
||||
this.$emit('input', this.currentValue);
|
||||
this.$emit('update:modelValue', this.currentValue);
|
||||
this.$emit('update:show', false);
|
||||
},
|
||||
onSheetOpen(event) {
|
||||
this.currentValue = this.value;
|
||||
this.currentValue = this.modelValue;
|
||||
this.scrollToSelectedItem(event.$el);
|
||||
},
|
||||
onSheetClosed() {
|
||||
|
||||
@@ -1,56 +1,41 @@
|
||||
<template>
|
||||
<f7-sheet style="height:auto" :opened="show"
|
||||
@sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
|
||||
<f7-sheet swipe-to-close swipe-handler=".swipe-handler" style="height:auto"
|
||||
:opened="show" @sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
|
||||
<div class="swipe-handler" style="z-index: 10"></div>
|
||||
<f7-page-content>
|
||||
<div class="display-flex padding justify-content-space-between align-items-center">
|
||||
<div style="font-size: 18px" v-if="title"><b>{{ title }}</b></div>
|
||||
</div>
|
||||
<div class="padding-horizontal padding-bottom">
|
||||
<p class="no-margin-top margin-bottom-half" v-if="hint">{{ hint }}</p>
|
||||
<p class="no-margin-top" v-if="hint">{{ hint }}</p>
|
||||
<p class="no-margin-top margin-bottom" v-if="beginDateTime && endDateTime">
|
||||
<span>{{ beginDateTime }}</span>
|
||||
<span> - </span>
|
||||
<span>{{ endDateTime }}</span>
|
||||
</p>
|
||||
<slot></slot>
|
||||
<f7-list no-hairlines inline-labels class="no-margin-top margin-bottom">
|
||||
<f7-list-input
|
||||
:label="$t('Begin Time')"
|
||||
type="datepicker"
|
||||
class="date-range-sheet-time-item"
|
||||
:calendar-params="{
|
||||
timePicker: true,
|
||||
dateFormat: $t('input-format.datetime.long'),
|
||||
firstDay: defaultFirstDayOfWeek,
|
||||
toolbarCloseText: $t('Done'),
|
||||
timePickerPlaceholder: $t('Select Time'),
|
||||
timePickerFormat: $locale.getInputTimeIntlDateTimeFormatOptions(),
|
||||
monthNames: $locale.getAllLongMonthNames(),
|
||||
monthNamesShort: $locale.getAllShortMonthNames(),
|
||||
dayNames: $locale.getAllLongWeekdayNames(),
|
||||
dayNamesShort: $locale.getAllShortWeekdayNames()}"
|
||||
:value="currentMinDate"
|
||||
@calendar:change="currentMinDate = $event"
|
||||
>
|
||||
</f7-list-input>
|
||||
|
||||
<f7-list-input
|
||||
:label="$t('End Time')"
|
||||
type="datepicker"
|
||||
class="date-range-sheet-time-item"
|
||||
:calendar-params="{
|
||||
timePicker: true,
|
||||
dateFormat: $t('input-format.datetime.long'),
|
||||
firstDay: defaultFirstDayOfWeek,
|
||||
toolbarCloseText: $t('Done'),
|
||||
timePickerPlaceholder: $t('Select Time'),
|
||||
timePickerFormat: $locale.getInputTimeIntlDateTimeFormatOptions(),
|
||||
monthNames: $locale.getAllLongMonthNames(),
|
||||
monthNamesShort: $locale.getAllShortMonthNames(),
|
||||
dayNames: $locale.getAllLongWeekdayNames(),
|
||||
dayNamesShort: $locale.getAllShortWeekdayNames()}"
|
||||
:value="currentMaxDate"
|
||||
@calendar:change="currentMaxDate = $event"
|
||||
>
|
||||
</f7-list-input>
|
||||
</f7-list>
|
||||
<vue-date-picker range inline enable-seconds auto-apply
|
||||
month-name-format="long"
|
||||
six-weeks="center"
|
||||
class="justify-content-center margin-bottom"
|
||||
:clearable="false"
|
||||
:dark="isDarkMode"
|
||||
:week-start="firstDayOfWeek"
|
||||
:year-range="yearRange"
|
||||
:day-names="dayNames"
|
||||
:is24="is24Hour"
|
||||
:partial-range="false"
|
||||
:preset-ranges="presetRanges"
|
||||
v-model="dateRange">
|
||||
<template #month="{ text }">
|
||||
{{ $t(`datetime.${text}.short`) }}
|
||||
</template>
|
||||
<template #month-overlay-value="{ text }">
|
||||
{{ $t(`datetime.${text}.short`) }}
|
||||
</template>
|
||||
</vue-date-picker>
|
||||
<f7-button large fill
|
||||
:class="{ 'disabled': !currentMinDate || !currentMaxDate }"
|
||||
:class="{ 'disabled': !dateRange[0] || !dateRange[1] }"
|
||||
:text="$t('Continue')"
|
||||
@click="confirm">
|
||||
</f7-button>
|
||||
@@ -71,6 +56,10 @@ export default {
|
||||
'hint',
|
||||
'show'
|
||||
],
|
||||
emits: [
|
||||
'update:show',
|
||||
'dateRange:change'
|
||||
],
|
||||
data() {
|
||||
const self = this;
|
||||
let minDate = self.$utilities.getTodayFirstUnixTime();
|
||||
@@ -84,65 +73,86 @@ export default {
|
||||
maxDate = self.maxTime;
|
||||
}
|
||||
|
||||
minDate = self.$utilities.getDummyUnixTimeForLocalUsage(minDate, self.$utilities.getTimezoneOffsetMinutes(), self.$utilities.getBrowserTimezoneOffsetMinutes());
|
||||
maxDate = self.$utilities.getDummyUnixTimeForLocalUsage(maxDate, self.$utilities.getTimezoneOffsetMinutes(), self.$utilities.getBrowserTimezoneOffsetMinutes());
|
||||
|
||||
return {
|
||||
currentMinDate: [self.$utilities.getLocalDatetimeFromUnixTime(minDate)],
|
||||
currentMaxDate: [self.$utilities.getLocalDatetimeFromUnixTime(maxDate)]
|
||||
yearRange: [
|
||||
2000,
|
||||
this.$utilities.getYear(this.$utilities.getCurrentDateTime()) + 1
|
||||
],
|
||||
dateRange: [
|
||||
this.$utilities.getLocalDatetimeFromUnixTime(this.$utilities.getDummyUnixTimeForLocalUsage(minDate, this.$utilities.getTimezoneOffsetMinutes(), this.$utilities.getBrowserTimezoneOffsetMinutes())),
|
||||
this.$utilities.getLocalDatetimeFromUnixTime(this.$utilities.getDummyUnixTimeForLocalUsage(maxDate, this.$utilities.getTimezoneOffsetMinutes(), this.$utilities.getBrowserTimezoneOffsetMinutes()))
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
defaultFirstDayOfWeek() {
|
||||
return this.$store.getters.currentUserFirstDayOfWeek;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'currentMinDate': function (newValue) {
|
||||
if (!newValue) {
|
||||
this.currentMinDate = [this.$utilities.getLocalDatetimeFromUnixTime(this.$utilities.getCurrentUnixTime())];
|
||||
}
|
||||
isDarkMode() {
|
||||
return this.$root.isDarkMode;
|
||||
},
|
||||
'currentMaxDate': function (newValue) {
|
||||
if (!newValue) {
|
||||
this.currentMaxDate = [this.$utilities.getLocalDatetimeFromUnixTime(this.$utilities.getCurrentUnixTime())];
|
||||
}
|
||||
firstDayOfWeek() {
|
||||
return this.$store.getters.currentUserFirstDayOfWeek;
|
||||
},
|
||||
dayNames() {
|
||||
return this.$utilities.arrangeArrayWithNewStartIndex(this.$locale.getAllMinWeekdayNames(), this.firstDayOfWeek);
|
||||
},
|
||||
is24Hour() {
|
||||
return this.$locale.isLongTime24HourFormat();
|
||||
},
|
||||
beginDateTime() {
|
||||
const actualBeginUnixTime = this.$utilities.getActualUnixTimeForStore(this.$utilities.getUnixTime(this.dateRange[0]), this.$utilities.getTimezoneOffsetMinutes(), this.$utilities.getBrowserTimezoneOffsetMinutes());
|
||||
return this.$utilities.formatUnixTime(actualBeginUnixTime, this.$locale.getLongDateTimeFormat());
|
||||
},
|
||||
endDateTime() {
|
||||
const actualEndUnixTime = this.$utilities.getActualUnixTimeForStore(this.$utilities.getUnixTime(this.dateRange[1]), this.$utilities.getTimezoneOffsetMinutes(), this.$utilities.getBrowserTimezoneOffsetMinutes());
|
||||
return this.$utilities.formatUnixTime(actualEndUnixTime, this.$locale.getLongDateTimeFormat());
|
||||
},
|
||||
presetRanges() {
|
||||
const presetRanges = [];
|
||||
|
||||
[
|
||||
this.$constants.datetime.allDateRanges.Today,
|
||||
this.$constants.datetime.allDateRanges.LastSevenDays,
|
||||
this.$constants.datetime.allDateRanges.LastThirtyDays,
|
||||
this.$constants.datetime.allDateRanges.ThisWeek,
|
||||
this.$constants.datetime.allDateRanges.ThisMonth,
|
||||
this.$constants.datetime.allDateRanges.ThisYear
|
||||
].forEach(dateRangeType => {
|
||||
const dateRange = this.$utilities.getDateRangeByDateType(dateRangeType.type, this.firstDayOfWeek);
|
||||
|
||||
presetRanges.push({
|
||||
label: this.$t(dateRangeType.name),
|
||||
range: [
|
||||
this.$utilities.getLocalDatetimeFromUnixTime(this.$utilities.getDummyUnixTimeForLocalUsage(dateRange.minTime, this.$utilities.getTimezoneOffsetMinutes(), this.$utilities.getBrowserTimezoneOffsetMinutes())),
|
||||
this.$utilities.getLocalDatetimeFromUnixTime(this.$utilities.getDummyUnixTimeForLocalUsage(dateRange.maxTime, this.$utilities.getTimezoneOffsetMinutes(), this.$utilities.getBrowserTimezoneOffsetMinutes()))
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
return presetRanges;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSheetOpen() {
|
||||
if (this.minTime) {
|
||||
const minTime = this.$utilities.getDummyUnixTimeForLocalUsage(this.minTime, this.$utilities.getTimezoneOffsetMinutes(), this.$utilities.getBrowserTimezoneOffsetMinutes());
|
||||
this.currentMinDate = [this.$utilities.getLocalDatetimeFromUnixTime(minTime)];
|
||||
this.dateRange[0] = this.$utilities.getLocalDatetimeFromUnixTime(this.$utilities.getDummyUnixTimeForLocalUsage(this.minTime, this.$utilities.getTimezoneOffsetMinutes(), this.$utilities.getBrowserTimezoneOffsetMinutes()));
|
||||
}
|
||||
|
||||
if (this.maxTime) {
|
||||
const maxTime = this.$utilities.getDummyUnixTimeForLocalUsage(this.maxTime, this.$utilities.getTimezoneOffsetMinutes(), this.$utilities.getBrowserTimezoneOffsetMinutes());
|
||||
this.currentMaxDate = [this.$utilities.getLocalDatetimeFromUnixTime(maxTime)];
|
||||
this.dateRange[1] = this.$utilities.getLocalDatetimeFromUnixTime(this.$utilities.getDummyUnixTimeForLocalUsage(this.maxTime, this.$utilities.getTimezoneOffsetMinutes(), this.$utilities.getBrowserTimezoneOffsetMinutes()));
|
||||
}
|
||||
},
|
||||
onSheetClosed() {
|
||||
this.$emit('update:show', false);
|
||||
},
|
||||
confirm() {
|
||||
if (!this.currentMinDate || !this.currentMaxDate) {
|
||||
if (!this.dateRange[0] || !this.dateRange[1]) {
|
||||
return;
|
||||
}
|
||||
|
||||
let currentMinDate = this.currentMinDate;
|
||||
const currentMinDate = this.dateRange[0];
|
||||
const currentMaxDate = this.dateRange[1];
|
||||
|
||||
if (this.$utilities.isArray(this.currentMinDate)) {
|
||||
currentMinDate = this.currentMinDate[0];
|
||||
}
|
||||
|
||||
let currentMaxDate = this.currentMaxDate;
|
||||
|
||||
if (this.$utilities.isArray(this.currentMaxDate)) {
|
||||
currentMaxDate = this.currentMaxDate[0];
|
||||
}
|
||||
|
||||
let minUnixTime = this.$utilities.getMinuteFirstUnixTime(currentMinDate);
|
||||
let maxUnixTime = this.$utilities.getMinuteLastUnixTime(currentMaxDate);
|
||||
let minUnixTime = this.$utilities.getUnixTime(currentMinDate);
|
||||
let maxUnixTime = this.$utilities.getUnixTime(currentMaxDate);
|
||||
|
||||
if (minUnixTime < 0 || maxUnixTime < 0) {
|
||||
this.$toast('Date is too early');
|
||||
@@ -160,9 +170,3 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.list .date-range-sheet-time-item > .item-content {
|
||||
padding-left: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<f7-sheet swipe-to-close swipe-handler=".swipe-handler" class="date-time-selection-sheet" style="height:auto"
|
||||
:opened="show" @sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
|
||||
<f7-toolbar>
|
||||
<div class="swipe-handler"></div>
|
||||
<div class="left">
|
||||
<f7-link :text="$t('Now')" @click="setCurrentTime"></f7-link>
|
||||
</div>
|
||||
<div class="right">
|
||||
<f7-link :text="$t('Done')" @click="confirm"></f7-link>
|
||||
</div>
|
||||
</f7-toolbar>
|
||||
<f7-page-content>
|
||||
<vue-date-picker inline enable-seconds auto-apply
|
||||
month-name-format="long"
|
||||
six-weeks="center"
|
||||
class="justify-content-center"
|
||||
:clearable="false"
|
||||
:dark="isDarkMode"
|
||||
:week-start="firstDayOfWeek"
|
||||
:year-range="yearRange"
|
||||
:day-names="dayNames"
|
||||
:is24="is24Hour"
|
||||
v-model="dateTime">
|
||||
<template #month="{ text }">
|
||||
{{ $t(`datetime.${text}.short`) }}
|
||||
</template>
|
||||
<template #month-overlay-value="{ text }">
|
||||
{{ $t(`datetime.${text}.short`) }}
|
||||
</template>
|
||||
</vue-date-picker>
|
||||
</f7-page-content>
|
||||
</f7-sheet>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: [
|
||||
'modelValue',
|
||||
'show'
|
||||
],
|
||||
emits: [
|
||||
'update:modelValue',
|
||||
'update:show'
|
||||
],
|
||||
data() {
|
||||
const self = this;
|
||||
let value = self.$utilities.getCurrentUnixTime();
|
||||
|
||||
if (self.modelValue) {
|
||||
value = self.modelValue;
|
||||
}
|
||||
|
||||
return {
|
||||
yearRange: [
|
||||
2000,
|
||||
this.$utilities.getYear(this.$utilities.getCurrentDateTime()) + 1
|
||||
],
|
||||
dateTime: this.$utilities.getLocalDatetimeFromUnixTime(value),
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isDarkMode() {
|
||||
return this.$root.isDarkMode;
|
||||
},
|
||||
firstDayOfWeek() {
|
||||
return this.$store.getters.currentUserFirstDayOfWeek;
|
||||
},
|
||||
dayNames() {
|
||||
return this.$utilities.arrangeArrayWithNewStartIndex(this.$locale.getAllMinWeekdayNames(), this.firstDayOfWeek);
|
||||
},
|
||||
is24Hour() {
|
||||
return this.$locale.isLongTime24HourFormat();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSheetOpen() {
|
||||
if (this.modelValue) {
|
||||
this.dateTime = this.$utilities.getLocalDatetimeFromUnixTime(this.modelValue)
|
||||
}
|
||||
},
|
||||
onSheetClosed() {
|
||||
this.$emit('update:show', false);
|
||||
},
|
||||
setCurrentTime() {
|
||||
this.dateTime = this.$utilities.getLocalDatetimeFromUnixTime(this.$utilities.getCurrentUnixTime())
|
||||
},
|
||||
confirm() {
|
||||
if (!this.dateTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
const unixTime = this.$utilities.getUnixTime(this.dateTime);
|
||||
|
||||
if (unixTime < 0) {
|
||||
this.$toast('Date is too early');
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit('update:modelValue', unixTime);
|
||||
this.$emit('update:show', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.date-time-selection-sheet .dp__menu {
|
||||
border: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,27 +1,27 @@
|
||||
<template>
|
||||
<f7-sheet :class="{ 'icon-selection-huge-sheet': hugeIconRows }" :opened="show" @sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
|
||||
<f7-sheet swipe-to-close swipe-handler=".swipe-handler"
|
||||
:class="heightClass" :opened="show"
|
||||
@sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
|
||||
<f7-toolbar>
|
||||
<div class="swipe-handler"></div>
|
||||
<div class="left"></div>
|
||||
<div class="right">
|
||||
<f7-link sheet-close :text="$t('Done')"></f7-link>
|
||||
</div>
|
||||
</f7-toolbar>
|
||||
<f7-page-content>
|
||||
<f7-block class="margin-vertical">
|
||||
<f7-row class="padding-vertical-half padding-horizontal-half"
|
||||
:class="{ 'row-has-selected-item': hasSelectedIcon(row) }"
|
||||
v-for="(row, idx) in allIconRows" :key="idx">
|
||||
<f7-col class="text-align-center" v-for="iconInfo in row" :key="iconInfo.id">
|
||||
<f7-icon :icon="iconInfo.icon"
|
||||
:style="color | iconStyle('default', 'var(--default-icon-color)')"
|
||||
@click.native="onIconClicked(iconInfo)">
|
||||
<f7-block class="margin-vertical no-padding">
|
||||
<div class="grid grid-cols-7 padding-vertical-half padding-horizontal-half"
|
||||
:class="{ 'row-has-selected-item': hasSelectedIcon(row) }"
|
||||
:key="idx" v-for="(row, idx) in allIconRows">
|
||||
<div class="text-align-center" :key="iconInfo.id" v-for="iconInfo in row">
|
||||
<ItemIcon icon-type="fixed" :icon-id="iconInfo.icon" :color="color" @click="onIconClicked(iconInfo)">
|
||||
<f7-badge color="default" class="right-bottom-icon" v-if="currentValue && currentValue === iconInfo.id">
|
||||
<f7-icon f7="checkmark_alt"></f7-icon>
|
||||
</f7-badge>
|
||||
</f7-icon>
|
||||
</f7-col>
|
||||
<f7-col v-for="idx in (itemPerRow - row.length)" :key="idx"></f7-col>
|
||||
</f7-row>
|
||||
</ItemIcon>
|
||||
</div>
|
||||
</div>
|
||||
</f7-block>
|
||||
</f7-page-content>
|
||||
</f7-sheet>
|
||||
@@ -30,17 +30,21 @@
|
||||
<script>
|
||||
export default {
|
||||
props: [
|
||||
'value',
|
||||
'modelValue',
|
||||
'color',
|
||||
'columnCount',
|
||||
'show',
|
||||
'allIconInfos'
|
||||
],
|
||||
emits: [
|
||||
'update:modelValue',
|
||||
'update:show'
|
||||
],
|
||||
data() {
|
||||
const self = this;
|
||||
|
||||
return {
|
||||
currentValue: self.value,
|
||||
currentValue: self.modelValue,
|
||||
itemPerRow: self.columnCount || 7
|
||||
}
|
||||
},
|
||||
@@ -71,18 +75,24 @@ export default {
|
||||
|
||||
return ret;
|
||||
},
|
||||
hugeIconRows() {
|
||||
return this.allIconRows.length > 10;
|
||||
heightClass() {
|
||||
if (this.allIconRows.length > 10) {
|
||||
return 'icon-selection-huge-sheet';
|
||||
} else if (this.allIconRows.length > 6) {
|
||||
return 'icon-selection-large-sheet';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onIconClicked(iconInfo) {
|
||||
this.currentValue = iconInfo.id;
|
||||
this.$emit('input', this.currentValue);
|
||||
this.$emit('update:modelValue', this.currentValue);
|
||||
this.$emit('update:show', false);
|
||||
},
|
||||
onSheetOpen(event) {
|
||||
this.currentValue = this.value;
|
||||
this.currentValue = this.modelValue;
|
||||
this.scrollToSelectedItem(event.$el);
|
||||
},
|
||||
onSheetClosed() {
|
||||
@@ -128,6 +138,10 @@ export default {
|
||||
|
||||
<style>
|
||||
@media (min-height: 630px) {
|
||||
.icon-selection-large-sheet {
|
||||
height: 310px;
|
||||
}
|
||||
|
||||
.icon-selection-huge-sheet {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
<template>
|
||||
<f7-sheet style="height:auto" :opened="show" @sheet:closed="onSheetClosed">
|
||||
<f7-page-content>
|
||||
<f7-sheet swipe-to-close swipe-handler=".swipe-handler" style="height:auto"
|
||||
:opened="show" @sheet:closed="onSheetClosed">
|
||||
<div class="swipe-handler" style="z-index: 10"></div>
|
||||
<f7-page-content class="margin-top no-padding-top">
|
||||
<div class="display-flex padding justify-content-space-between align-items-center">
|
||||
<div style="font-size: 18px" v-if="title"><b>{{ title }}</b></div>
|
||||
</div>
|
||||
<div class="padding-horizontal padding-bottom">
|
||||
<p class="no-margin-top margin-bottom-half" v-if="hint">
|
||||
<span>{{ hint }}</span>
|
||||
<f7-link class="icon-after-text"
|
||||
<f7-link id="copy-to-clipboard-icon" ref="copyToClipboardIcon"
|
||||
class="icon-after-text"
|
||||
icon-only icon-f7="doc_on_doc" icon-size="16px"
|
||||
v-if="enableCopy"
|
||||
v-clipboard:copy="information" v-clipboard:success="onCopied"></f7-link>
|
||||
></f7-link>
|
||||
</p>
|
||||
<textarea class="information-content full-line" :rows="rowCount" readonly="readonly" v-model="information"></textarea>
|
||||
<textarea class="information-content full-line" readonly="readonly" :rows="rowCount" :value="information"></textarea>
|
||||
<div class="margin-top text-align-center">
|
||||
<f7-link @click="cancel" :text="$t('Close')"></f7-link>
|
||||
</div>
|
||||
@@ -31,14 +34,53 @@ export default {
|
||||
'enableCopy',
|
||||
'show'
|
||||
],
|
||||
emits: [
|
||||
'update:show',
|
||||
'info:copied'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
clipboardHolder: null
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.makeCopyToClipboardClickable();
|
||||
},
|
||||
updated() {
|
||||
this.makeCopyToClipboardClickable();
|
||||
},
|
||||
watch: {
|
||||
'information': function (newValue) {
|
||||
if (this.clipboardHolder) {
|
||||
this.$utilities.changeClipboardObjectText(this.clipboardHolder, newValue);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSheetClosed() {
|
||||
this.$emit('update:show', false);
|
||||
},
|
||||
onCopied() {
|
||||
this.$emit('info:copied');
|
||||
this.close();
|
||||
},
|
||||
cancel() {
|
||||
this.close();
|
||||
},
|
||||
makeCopyToClipboardClickable() {
|
||||
const self = this;
|
||||
|
||||
if (self.clipboardHolder) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.$refs.copyToClipboardIcon) {
|
||||
self.clipboardHolder = self.$utilities.makeButtonCopyToClipboard({
|
||||
el: '#copy-to-clipboard-icon',
|
||||
text: self.information,
|
||||
successCallback: function () {
|
||||
self.$emit('info:copied');
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
close() {
|
||||
this.$emit('update:show', false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<f7-icon :f7="f7Icon" :icon="icon" :style="style">
|
||||
<slot></slot>
|
||||
</f7-icon>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: [
|
||||
'iconType',
|
||||
'iconId',
|
||||
'color',
|
||||
'defaultColor',
|
||||
'additionalColorAttr'
|
||||
],
|
||||
computed: {
|
||||
f7Icon() {
|
||||
if (this.iconType === 'fixed-f7') {
|
||||
return this.iconId;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
icon() {
|
||||
if (this.iconType === 'account') {
|
||||
return this.getAccountIcon(this.iconId);
|
||||
} else if (this.iconType === 'category') {
|
||||
return this.getCategoryIcon(this.iconId);
|
||||
} else if (this.iconType === 'fixed') {
|
||||
return this.iconId;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
style() {
|
||||
let defaultColor = 'var(--default-icon-color)';
|
||||
|
||||
if (this.defaultColor) {
|
||||
defaultColor = this.defaultColor;
|
||||
}
|
||||
|
||||
if (this.iconType === 'account') {
|
||||
return this.getAccountIconStyle(this.color, defaultColor, this.additionalColorAttr);
|
||||
} else if (this.iconType === 'category') {
|
||||
return this.getCategoryIconStyle(this.color, defaultColor, this.additionalColorAttr);
|
||||
} else {
|
||||
return this.getDefaultIconStyle(this.color, defaultColor, this.additionalColorAttr);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getAccountIcon(iconId) {
|
||||
if (this.$utilities.isNumber(iconId)) {
|
||||
iconId = iconId.toString();
|
||||
}
|
||||
|
||||
if (!this.$constants.icons.allAccountIcons[iconId]) {
|
||||
return this.$constants.icons.defaultAccountIcon.icon;
|
||||
}
|
||||
|
||||
return this.$constants.icons.allAccountIcons[iconId].icon;
|
||||
},
|
||||
getCategoryIcon(iconId) {
|
||||
if (this.$utilities.isNumber(iconId)) {
|
||||
iconId = iconId.toString();
|
||||
}
|
||||
|
||||
if (!this.$constants.icons.allCategoryIcons[iconId]) {
|
||||
return this.$constants.icons.defaultCategoryIcon.icon;
|
||||
}
|
||||
|
||||
return this.$constants.icons.allCategoryIcons[iconId].icon;
|
||||
},
|
||||
getAccountIconStyle(color, defaultColor, additionalColorAttr) {
|
||||
if (color && color !== this.$constants.colors.defaultAccountColor) {
|
||||
color = '#' + color;
|
||||
} else {
|
||||
color = defaultColor;
|
||||
}
|
||||
|
||||
const ret = {
|
||||
color: color
|
||||
};
|
||||
|
||||
if (additionalColorAttr) {
|
||||
ret[additionalColorAttr] = color;
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
getCategoryIconStyle(color, defaultColor, additionalColorAttr) {
|
||||
if (color && color !== this.$constants.colors.defaultCategoryColor) {
|
||||
color = '#' + color;
|
||||
} else {
|
||||
color = defaultColor;
|
||||
}
|
||||
|
||||
const ret = {
|
||||
color: color
|
||||
};
|
||||
|
||||
if (additionalColorAttr) {
|
||||
ret[additionalColorAttr] = color;
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
getDefaultIconStyle(color, defaultColor, additionalColorAttr) {
|
||||
if (color && color !== this.$constants.colors.defaultColor) {
|
||||
color = '#' + color;
|
||||
} else {
|
||||
color = defaultColor;
|
||||
}
|
||||
|
||||
const ret = {
|
||||
color: color
|
||||
};
|
||||
|
||||
if (additionalColorAttr) {
|
||||
ret[additionalColorAttr] = color;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,25 +1,29 @@
|
||||
<template>
|
||||
<f7-sheet :class="{ 'list-item-selection-huge-sheet': hugeListItemRows }" :opened="show" @sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
|
||||
<f7-sheet swipe-to-close swipe-handler=".swipe-handler"
|
||||
:class="heightClass" :opened="show"
|
||||
@sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
|
||||
<f7-toolbar>
|
||||
<div class="swipe-handler"></div>
|
||||
<div class="left"></div>
|
||||
<div class="right">
|
||||
<f7-link sheet-close :text="$t('Done')"></f7-link>
|
||||
</div>
|
||||
</f7-toolbar>
|
||||
<f7-page-content>
|
||||
<f7-list no-hairlines class="no-margin-top no-margin-bottom">
|
||||
<f7-list dividers class="no-margin-vertical">
|
||||
<f7-list-item link="#" no-chevron
|
||||
v-for="(item, index) in items"
|
||||
:key="item | itemKeyValue(index, keyField, valueType)"
|
||||
:title="$tIf((titleField ? item[titleField] : item), titleI18n)"
|
||||
:value="getItemValue(item, index, valueField, valueType)"
|
||||
:class="{ 'list-item-selected': isSelected(item, index) }"
|
||||
:value="item | itemKeyValue(index, valueField, valueType)"
|
||||
:title="item | itemFieldContent(titleField, item, titleI18n)"
|
||||
:key="getItemValue(item, index, keyField, valueType)"
|
||||
v-for="(item, index) in items"
|
||||
@click="onItemClicked(item, index)">
|
||||
<f7-icon slot="media"
|
||||
:icon="item[iconField] | icon(iconType)"
|
||||
:style="item[colorField] | iconStyle(iconType, 'var(--default-icon-color)')"
|
||||
v-if="iconField"></f7-icon>
|
||||
<f7-icon slot="after" class="list-item-checked-icon" f7="checkmark_alt" v-if="isSelected(item, index)"></f7-icon>
|
||||
<template #content-start>
|
||||
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" :style="{ 'color': isSelected(item, index) ? '' : 'transparent' }"></f7-icon>
|
||||
</template>
|
||||
<template #media v-if="iconField">
|
||||
<ItemIcon :icon-type="iconType" :icon-id="item[iconField]" :color="item[colorField]"></ItemIcon>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
</f7-list>
|
||||
</f7-page-content>
|
||||
@@ -29,7 +33,7 @@
|
||||
<script>
|
||||
export default {
|
||||
props: [
|
||||
'value',
|
||||
'modelValue',
|
||||
'valueType', // item or index
|
||||
'keyField', // for value type == item
|
||||
'valueField', // for value type == item
|
||||
@@ -41,19 +45,38 @@ export default {
|
||||
'items',
|
||||
'show'
|
||||
],
|
||||
emits: [
|
||||
'update:modelValue',
|
||||
'update:show'
|
||||
],
|
||||
data() {
|
||||
const self = this;
|
||||
|
||||
return {
|
||||
currentValue: self.value
|
||||
currentValue: self.modelValue
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hugeListItemRows() {
|
||||
return this.items.length > 10;
|
||||
heightClass() {
|
||||
if (this.items.length > 10) {
|
||||
return 'list-item-selection-huge-sheet';
|
||||
} else if (this.items.length > 6) {
|
||||
return 'list-item-selection-large-sheet';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getItemValue(item, index, fieldName, valueType) {
|
||||
if (valueType === 'index') {
|
||||
return index;
|
||||
} else if (fieldName) {
|
||||
return item[fieldName];
|
||||
} else {
|
||||
return item;
|
||||
}
|
||||
},
|
||||
onItemClicked(item, index) {
|
||||
if (this.valueType === 'index') {
|
||||
this.currentValue = index;
|
||||
@@ -65,15 +88,15 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
this.$emit('input', this.currentValue);
|
||||
this.$emit('update:show', false);
|
||||
this.$emit('update:modelValue', this.currentValue);
|
||||
this.close();
|
||||
},
|
||||
onSheetOpen(event) {
|
||||
this.currentValue = this.value;
|
||||
this.currentValue = this.modelValue;
|
||||
this.scrollToSelectedItem(event.$el);
|
||||
},
|
||||
onSheetClosed() {
|
||||
this.$emit('update:show', false);
|
||||
this.close();
|
||||
},
|
||||
isSelected(item, index) {
|
||||
if (this.valueType === 'index') {
|
||||
@@ -106,17 +129,9 @@ export default {
|
||||
}
|
||||
|
||||
container.scrollTop(targetPos);
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
itemKeyValue(item, index, fieldName, valueType) {
|
||||
if (valueType === 'index') {
|
||||
return index;
|
||||
} else if (fieldName) {
|
||||
return item[fieldName];
|
||||
} else {
|
||||
return item;
|
||||
}
|
||||
},
|
||||
close() {
|
||||
this.$emit('update:show', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,6 +139,10 @@ export default {
|
||||
|
||||
<style>
|
||||
@media (min-height: 630px) {
|
||||
.list-item-selection-large-sheet {
|
||||
height: 310px;
|
||||
}
|
||||
|
||||
.list-item-selection-huge-sheet {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<f7-sheet swipe-to-close swipe-handler=".swipe-handler" style="height:auto"
|
||||
:opened="show" @sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
|
||||
<f7-toolbar>
|
||||
<div class="swipe-handler"></div>
|
||||
<div class="left"></div>
|
||||
<div class="right">
|
||||
<f7-link :text="$t('Done')" @click="save"></f7-link>
|
||||
</div>
|
||||
</f7-toolbar>
|
||||
<f7-page-content class="no-margin-vertical no-padding-vertical" v-if="knownMapProvider">
|
||||
<div ref="map" style="height: 400px; width: 100%"></div>
|
||||
</f7-page-content>
|
||||
<f7-page-content class="no-margin-top no-padding-top" v-else-if="!knownMapProvider">
|
||||
<div class="display-flex padding justify-content-space-between align-items-center">
|
||||
<div style="font-size: 18px"><b>{{ $t('Unsupported Map Provider') }}</b></div>
|
||||
</div>
|
||||
<div class="padding-horizontal padding-bottom">
|
||||
<p class="no-margin">{{ $t('Please refresh the page and try again. If the error is still displayed, make sure that server map settings are set correctly.') }}</p>
|
||||
<div class="margin-top text-align-center">
|
||||
<f7-link @click="close" :text="$t('Close')"></f7-link>
|
||||
</div>
|
||||
</div>
|
||||
</f7-page-content>
|
||||
</f7-sheet>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: [
|
||||
'modelValue',
|
||||
'show'
|
||||
],
|
||||
emits: [
|
||||
'update:modelValue',
|
||||
'update:show'
|
||||
],
|
||||
data() {
|
||||
let knownMapProvider = false;
|
||||
|
||||
if (this.$settings.getMapProvider() === 'openstreetmap') {
|
||||
knownMapProvider = true;
|
||||
}
|
||||
|
||||
return {
|
||||
knownMapProvider: knownMapProvider,
|
||||
leaflet: null,
|
||||
tileLayer: null,
|
||||
zoomControl: null,
|
||||
attribution: null,
|
||||
marker: null,
|
||||
initCenter: [ 0, 0 ],
|
||||
zoomLevel: 1
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
save() {
|
||||
this.$emit('update:show', false);
|
||||
},
|
||||
onSheetOpen() {
|
||||
let isFirstInit = false;
|
||||
let centerChanged = false;
|
||||
|
||||
if (this.modelValue && (this.modelValue.longitude || this.modelValue.latitude)) {
|
||||
if (this.initCenter[0] !== this.modelValue.latitude || this.initCenter[1] !== this.modelValue.longitude) {
|
||||
this.initCenter[0] = this.modelValue.latitude;
|
||||
this.initCenter[1] = this.modelValue.longitude;
|
||||
this.zoomLevel = 14;
|
||||
|
||||
centerChanged = true;
|
||||
}
|
||||
} else if (!this.modelValue || (!this.modelValue.longitude && !this.modelValue.latitude)) {
|
||||
if (this.initCenter[0] || this.initCenter[1]) {
|
||||
this.initCenter[0] = 0;
|
||||
this.initCenter[1] = 0;
|
||||
this.zoomLevel = 1;
|
||||
|
||||
centerChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.knownMapProvider && this.$settings.getMapProvider() === 'openstreetmap') {
|
||||
if (!this.leaflet) {
|
||||
const mapContainer = this.$refs.map;
|
||||
|
||||
this.leaflet = this.$map.leaflet.map(mapContainer, {
|
||||
attributionControl: false,
|
||||
zoomControl: false
|
||||
});
|
||||
|
||||
const mapTileImageUrl = this.$map.generateOpenStreetMapTileImageUrl();
|
||||
|
||||
this.tileLayer = this.$map.leaflet.tileLayer(mapTileImageUrl.url, {
|
||||
subdomains: mapTileImageUrl.subDomains,
|
||||
maxZoom: 19
|
||||
});
|
||||
this.tileLayer.addTo(this.leaflet);
|
||||
|
||||
this.zoomControl = this.$map.leaflet.control.zoom({
|
||||
zoomInTitle: this.$t('Zoom in'),
|
||||
zoomOutTitle: this.$t('Zoom out'),
|
||||
});
|
||||
this.zoomControl.addTo(this.leaflet);
|
||||
|
||||
this.attribution = this.$map.leaflet.control.attribution({
|
||||
prefix: false
|
||||
});
|
||||
this.attribution.addAttribution('© <a href="http://www.openstreetmap.org/copyright" class="external" target="_blank">OpenStreetMap</a>');
|
||||
this.attribution.addTo(this.leaflet);
|
||||
|
||||
isFirstInit = true;
|
||||
}
|
||||
|
||||
if (isFirstInit || centerChanged) {
|
||||
this.leaflet.setView(this.initCenter, this.zoomLevel);
|
||||
}
|
||||
|
||||
if (centerChanged && this.zoomLevel > 1) {
|
||||
if (!this.marker) {
|
||||
const markerIcon = this.$map.leaflet.icon({
|
||||
iconUrl: 'img/map-marker-icon.png',
|
||||
iconRetinaUrl: 'img/map-marker-icon-2x.png',
|
||||
iconSize: [25, 32],
|
||||
iconAnchor: [12, 32],
|
||||
shadowUrl: 'img/map-marker-shadow.png',
|
||||
shadowSize: [41, 32]
|
||||
});
|
||||
this.marker = this.$map.leaflet.marker(this.initCenter, {
|
||||
icon: markerIcon
|
||||
});
|
||||
this.marker.addTo(this.leaflet);
|
||||
} else {
|
||||
this.marker.setLatLng(this.initCenter);
|
||||
}
|
||||
} else if (centerChanged && this.zoomLevel <= 1) {
|
||||
if (this.marker) {
|
||||
this.marker.remove();
|
||||
this.marker = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.knownMapProvider = false;
|
||||
}
|
||||
},
|
||||
onSheetClosed() {
|
||||
this.close();
|
||||
},
|
||||
close() {
|
||||
this.$emit('update:show', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,10 +1,12 @@
|
||||
<template>
|
||||
<f7-sheet class="numpad-sheet" :opened="show" @sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
|
||||
<f7-page-content class="no-margin no-padding-top">
|
||||
<f7-row class="numpad-values">
|
||||
<f7-sheet swipe-to-close swipe-handler=".swipe-handler" class="numpad-sheet" style="height: auto"
|
||||
:opened="show" @sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
|
||||
<div class="swipe-handler" style="z-index: 10"></div>
|
||||
<f7-page-content class="margin-top no-padding-top">
|
||||
<div class="numpad-values">
|
||||
<span class="numpad-value" :style="{ fontSize: currentDisplayFontSize + 'px' }">{{ currentDisplay }}</span>
|
||||
</f7-row>
|
||||
<f7-row class="numpad-buttons">
|
||||
</div>
|
||||
<div class="numpad-buttons">
|
||||
<f7-button class="numpad-button numpad-button-num" @click="inputNum(7)">
|
||||
<span class="numpad-button-text numpad-button-text-normal">7</span>
|
||||
</f7-button>
|
||||
@@ -47,7 +49,7 @@
|
||||
<f7-button class="numpad-button numpad-button-num" @click="inputNum(0)">
|
||||
<span class="numpad-button-text numpad-button-text-normal">0</span>
|
||||
</f7-button>
|
||||
<f7-button class="numpad-button numpad-button-num" @click="backspace" @taphold.native="clear()">
|
||||
<f7-button class="numpad-button numpad-button-num" @click="backspace" @taphold="clear()">
|
||||
<span class="numpad-button-text numpad-button-text-normal">
|
||||
<f7-icon f7="delete_left"></f7-icon>
|
||||
</span>
|
||||
@@ -55,7 +57,7 @@
|
||||
<f7-button class="numpad-button numpad-button-confirm no-right-border no-bottom-border" fill @click="confirm()">
|
||||
<span :class="{ 'numpad-button-text': true, 'numpad-button-text-confirm': !currentSymbol }">{{ confirmText }}</span>
|
||||
</f7-button>
|
||||
</f7-row>
|
||||
</div>
|
||||
</f7-page-content>
|
||||
</f7-sheet>
|
||||
</template>
|
||||
@@ -63,18 +65,22 @@
|
||||
<script>
|
||||
export default {
|
||||
props: [
|
||||
'value',
|
||||
'modelValue',
|
||||
'minValue',
|
||||
'maxValue',
|
||||
'show'
|
||||
],
|
||||
emits: [
|
||||
'update:modelValue',
|
||||
'update:show'
|
||||
],
|
||||
data() {
|
||||
const self = this;
|
||||
|
||||
return {
|
||||
previousValue: '',
|
||||
currentSymbol: '',
|
||||
currentValue: self.getStringValue(self.value)
|
||||
currentValue: self.getStringValue(self.modelValue)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -103,7 +109,7 @@ export default {
|
||||
if (this.currentSymbol) {
|
||||
return '=';
|
||||
} else {
|
||||
return this.$i18n.t('OK');
|
||||
return this.$t('OK');
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -125,7 +131,7 @@ export default {
|
||||
return str;
|
||||
}
|
||||
|
||||
let integer = str.substr(0, dotPos);
|
||||
let integer = str.substring(0, dotPos);
|
||||
let decimals = str.substring(dotPos + 1, str.length);
|
||||
let newDecimals = '';
|
||||
|
||||
@@ -232,7 +238,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
this.currentValue = this.currentValue.substr(0, this.currentValue.length - 1);
|
||||
this.currentValue = this.currentValue.substring(0, this.currentValue.length - 1);
|
||||
},
|
||||
clear() {
|
||||
this.currentValue = '';
|
||||
@@ -291,17 +297,20 @@ export default {
|
||||
} else {
|
||||
const value = this.$utilities.stringCurrencyToNumeric(this.currentValue);
|
||||
|
||||
this.$emit('input', value);
|
||||
this.$emit('update:show', false);
|
||||
this.$emit('update:modelValue', value);
|
||||
this.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
},
|
||||
close() {
|
||||
this.$emit('update:show', false);
|
||||
},
|
||||
onSheetOpen() {
|
||||
this.currentValue = this.getStringValue(this.value);
|
||||
this.currentValue = this.getStringValue(this.modelValue);
|
||||
},
|
||||
onSheetClosed() {
|
||||
this.$emit('update:show', false);
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -322,7 +331,6 @@ export default {
|
||||
padding-left: 16px;
|
||||
line-height: 1;
|
||||
height: 50px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
user-select: none;
|
||||
@@ -346,6 +354,7 @@ export default {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.numpad-button-num {
|
||||
@@ -371,7 +380,7 @@ export default {
|
||||
color: var(--f7-color-black);
|
||||
}
|
||||
|
||||
.theme-dark .numpad-button-text-normal {
|
||||
.dark .numpad-button-text-normal {
|
||||
color: var(--f7-color-white);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
<template>
|
||||
<f7-sheet style="height:auto" :opened="show"
|
||||
@sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
|
||||
<f7-page-content>
|
||||
<f7-sheet swipe-to-close swipe-handler=".swipe-handler" style="height:auto"
|
||||
:opened="show" @sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
|
||||
<div class="swipe-handler" style="z-index: 10"></div>
|
||||
<f7-page-content class="margin-top no-padding-top">
|
||||
<div class="display-flex padding justify-content-space-between align-items-center">
|
||||
<div style="font-size: 18px" v-if="title"><b>{{ title }}</b></div>
|
||||
</div>
|
||||
<div class="padding-horizontal padding-bottom">
|
||||
<p class="no-margin-top margin-bottom-half" v-if="hint">{{ hint }}</p>
|
||||
<p class="no-margin" v-if="hint">{{ hint }}</p>
|
||||
<slot></slot>
|
||||
<f7-list no-hairlines class="no-margin-top margin-bottom">
|
||||
<f7-list strong class="no-margin">
|
||||
<f7-list-input
|
||||
type="number"
|
||||
autocomplete="one-time-code"
|
||||
outline
|
||||
floating-label
|
||||
clear-button
|
||||
class="no-margin no-padding-bottom"
|
||||
:label="$t('Passcode')"
|
||||
:placeholder="$t('Passcode')"
|
||||
:value="currentPasscode"
|
||||
@input="currentPasscode = $event.target.value"
|
||||
@keyup.enter.native="confirm()"
|
||||
v-model:value="currentPasscode"
|
||||
@keyup.enter="confirm()"
|
||||
></f7-list-input>
|
||||
</f7-list>
|
||||
<f7-button large fill
|
||||
@@ -36,13 +39,18 @@
|
||||
<script>
|
||||
export default {
|
||||
props: [
|
||||
'value',
|
||||
'modelValue',
|
||||
'title',
|
||||
'hint',
|
||||
'confirmDisabled',
|
||||
'cancelDisabled',
|
||||
'show'
|
||||
],
|
||||
emits: [
|
||||
'update:modelValue',
|
||||
'update:show',
|
||||
'passcode:confirm'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
currentPasscode: ''
|
||||
@@ -53,17 +61,20 @@ export default {
|
||||
this.currentPasscode = '';
|
||||
},
|
||||
onSheetClosed() {
|
||||
this.$emit('update:show', false);
|
||||
this.close();
|
||||
},
|
||||
confirm() {
|
||||
if (!this.currentPasscode || this.confirmDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit('input', this.currentPasscode);
|
||||
this.$emit('update:modelValue', this.currentPasscode);
|
||||
this.$emit('passcode:confirm', this.currentPasscode);
|
||||
},
|
||||
cancel() {
|
||||
this.close();
|
||||
},
|
||||
close() {
|
||||
this.$emit('update:show', false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,31 @@
|
||||
<template>
|
||||
<f7-sheet style="height:auto" :opened="show"
|
||||
@sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
|
||||
<f7-page-content>
|
||||
<f7-sheet swipe-to-close swipe-handler=".swipe-handler" style="height:auto"
|
||||
:opened="show" @sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
|
||||
<div class="swipe-handler" style="z-index: 10"></div>
|
||||
<f7-page-content class="margin-top no-padding-top">
|
||||
<div class="display-flex padding justify-content-space-between align-items-center">
|
||||
<div style="font-size: 18px" v-if="title"><b>{{ title }}</b></div>
|
||||
</div>
|
||||
<div class="padding-horizontal padding-bottom">
|
||||
<p class="no-margin-top margin-bottom-half" v-if="hint">{{ hint }}</p>
|
||||
<f7-list no-hairlines class="no-margin-top margin-bottom">
|
||||
<p class="no-margin" v-if="hint">{{ hint }}</p>
|
||||
<f7-list strong class="no-margin">
|
||||
<f7-list-input
|
||||
type="password"
|
||||
autocomplete="current-password"
|
||||
outline
|
||||
floating-label
|
||||
clear-button
|
||||
:placeholder="$t('Password')"
|
||||
:value="currentPassword"
|
||||
@input="currentPassword = $event.target.value"
|
||||
@keyup.enter.native="confirm()"
|
||||
class="no-margin no-padding-bottom"
|
||||
:class="color ? 'color-' + color : ''"
|
||||
:label="$t('Current Password')"
|
||||
:placeholder="$t('Current Password')"
|
||||
v-model:value="currentPassword"
|
||||
@keyup.enter="confirm()"
|
||||
></f7-list-input>
|
||||
</f7-list>
|
||||
<f7-button large fill
|
||||
:class="{ 'disabled': !currentPassword || confirmDisabled }"
|
||||
:color="color || 'primary'"
|
||||
:text="$t('Continue')"
|
||||
@click="confirm">
|
||||
</f7-button>
|
||||
@@ -35,13 +40,19 @@
|
||||
<script>
|
||||
export default {
|
||||
props: [
|
||||
'value',
|
||||
'modelValue',
|
||||
'title',
|
||||
'hint',
|
||||
'color',
|
||||
'confirmDisabled',
|
||||
'cancelDisabled',
|
||||
'show'
|
||||
],
|
||||
emits: [
|
||||
'update:modelValue',
|
||||
'update:show',
|
||||
'password:confirm'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
currentPassword: ''
|
||||
@@ -52,17 +63,20 @@ export default {
|
||||
this.currentPassword = '';
|
||||
},
|
||||
onSheetClosed() {
|
||||
this.$emit('update:show', false);
|
||||
this.close();
|
||||
},
|
||||
confirm() {
|
||||
if (!this.currentPassword || this.confirmDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit('input', this.currentPassword);
|
||||
this.$emit('update:modelValue', this.currentPassword);
|
||||
this.$emit('password:confirm', this.currentPassword);
|
||||
},
|
||||
cancel() {
|
||||
this.close();
|
||||
},
|
||||
close() {
|
||||
this.$emit('update:show', false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,15 @@
|
||||
<circle class="pie-chart-background" cx="0" cy="0" :r="diameter"></circle>
|
||||
|
||||
<circle class="pie-chart-item"
|
||||
v-for="(item, idx) in validItems" :key="idx"
|
||||
fill="transparent"
|
||||
cx="0" cy="0"
|
||||
:r="diameter / 2"
|
||||
:stroke="item.color | defaultIconColor('var(--default-icon-color)')"
|
||||
:stroke="getColor(item.color)"
|
||||
:stroke-width="diameter"
|
||||
:stroke-dasharray="item | itemStrokeDash(circumference)"
|
||||
:stroke-dashoffset="item | itemDashOffset(validItems, circumference, itemCommonDashOffset)"
|
||||
:stroke-dasharray="getItemStrokeDash(item)"
|
||||
:stroke-dashoffset="getItemDashOffset(item, validItems, itemCommonDashOffset)"
|
||||
:key="idx"
|
||||
v-for="(item, idx) in validItems"
|
||||
@click="switchSelectedIndex(idx)">
|
||||
</circle>
|
||||
|
||||
@@ -48,8 +49,8 @@
|
||||
<span class="skeleton-text">Percent</span>
|
||||
</f7-chip>
|
||||
<f7-chip outline
|
||||
:text="(selectedItem.percent) | percent(2, '<0.01')"
|
||||
:style="(selectedItem ? selectedItem.color : '') | iconStyle('default', 'var(--default-icon-color)', '--f7-chip-outline-border-color')"
|
||||
:text="$utilities.formatPercent(selectedItem.percent, 2, '<0.01')"
|
||||
:style="getColorStyle(selectedItem ? selectedItem.color : '', '--f7-chip-outline-border-color')"
|
||||
v-else-if="!skeleton"></f7-chip>
|
||||
</p>
|
||||
<p v-else-if="!validItems || !validItems.length">
|
||||
@@ -59,7 +60,7 @@
|
||||
<span class="skeleton-text" v-if="skeleton">Name</span>
|
||||
<span v-else-if="!skeleton && selectedItem.name">{{ selectedItem.name }}</span>
|
||||
<span class="skeleton-text" v-if="skeleton">Value</span>
|
||||
<span v-else-if="!skeleton && showValue" :style="(selectedItem ? selectedItem.color : '') | iconStyle('default', 'var(--default-icon-color)')">{{ selectedItem.value | currency(selectedItem.currency || defaultCurrency) }}</span>
|
||||
<span v-else-if="!skeleton && showValue" :style="getColorStyle(selectedItem ? selectedItem.color : '')">{{ $locale.getDisplayCurrency(selectedItem.value, (selectedItem.currency || defaultCurrency)) }}</span>
|
||||
<f7-icon class="item-navigate-icon" f7="chevron_right" v-if="enableClickItem"></f7-icon>
|
||||
</f7-link>
|
||||
<f7-link :no-link-class="true" v-else-if="!validItems || !validItems.length">
|
||||
@@ -107,6 +108,9 @@ export default {
|
||||
'enableClickItem',
|
||||
'centerTextBackground',
|
||||
],
|
||||
emits: [
|
||||
'click'
|
||||
],
|
||||
data: function () {
|
||||
const diameter = 100;
|
||||
|
||||
@@ -195,8 +199,11 @@ export default {
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'items': function () {
|
||||
this.selectedIndex = 0;
|
||||
'items': {
|
||||
handler() {
|
||||
this.selectedIndex = 0;
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -216,14 +223,32 @@ export default {
|
||||
if (this.enableClickItem) {
|
||||
this.$emit('click', item.sourceItem);
|
||||
}
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
itemStrokeDash(item, circumference) {
|
||||
const length = item.actualPercent * circumference;
|
||||
return `${length} ${circumference - length}`;
|
||||
},
|
||||
itemDashOffset(item, items, circumference, offset) {
|
||||
getColor: function (color) {
|
||||
if (color && color !== this.$constants.colors.defaultColor) {
|
||||
color = '#' + color;
|
||||
} else {
|
||||
color = 'var(--default-icon-color)';
|
||||
}
|
||||
|
||||
return color;
|
||||
},
|
||||
getColorStyle: function (color, additionalFieldName) {
|
||||
const ret = {
|
||||
color: this.getColor(color)
|
||||
};
|
||||
|
||||
if (additionalFieldName) {
|
||||
ret[additionalFieldName] = ret.color;
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
getItemStrokeDash(item) {
|
||||
const length = item.actualPercent * this.circumference;
|
||||
return `${length} ${this.circumference - length}`;
|
||||
},
|
||||
getItemDashOffset(item, items, offset) {
|
||||
let allPreviousPercent = 0;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
@@ -237,17 +262,17 @@ export default {
|
||||
}
|
||||
|
||||
if (offset) {
|
||||
offset += circumference / 4;
|
||||
offset += this.circumference / 4;
|
||||
} else {
|
||||
offset = circumference / 4;
|
||||
offset = this.circumference / 4;
|
||||
}
|
||||
|
||||
if (allPreviousPercent <= 0) {
|
||||
return offset;
|
||||
}
|
||||
|
||||
const allPreviousLength = allPreviousPercent * circumference;
|
||||
return circumference - allPreviousLength + offset;
|
||||
const allPreviousLength = allPreviousPercent * this.circumference;
|
||||
return this.circumference - allPreviousLength + offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -314,7 +339,7 @@ export default {
|
||||
fill: #f0f0f0;
|
||||
}
|
||||
|
||||
.theme-dark .pie-chart-background {
|
||||
.dark .pie-chart-background {
|
||||
fill: #181818;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
<template>
|
||||
<div class="pin-code-input grid grid-gap" :class="'grid-cols-' + length">
|
||||
<div class="input input-outline input-with-value"
|
||||
:key="index" v-for="(code, index) in codes">
|
||||
<input min="0" maxlength="1" pattern="[0-9]*"
|
||||
:ref="`pin-code-input-${index}`"
|
||||
:value="codes[index].value"
|
||||
:type="codes[index].inputType"
|
||||
@keydown="onKeydown(index, $event)"
|
||||
@paste="onPaste(index, $event)"
|
||||
@change="onInput(index, $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: [
|
||||
'modelValue',
|
||||
'secure',
|
||||
'length'
|
||||
],
|
||||
emits: [
|
||||
'update:modelValue',
|
||||
'pincode:confirm'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
codes: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
finalPinCode() {
|
||||
let finalPinCode = '';
|
||||
|
||||
for (let i = 0; i < this.codes.length; i++) {
|
||||
if (this.codes[i].value) {
|
||||
finalPinCode += this.codes[i].value;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return finalPinCode;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'length': function (newValue) {
|
||||
this.init(newValue, this.modelValue);
|
||||
},
|
||||
'modelValue': function (newValue) {
|
||||
if (newValue === this.finalPinCode) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.init(this.length, newValue);
|
||||
},
|
||||
'codes': {
|
||||
handler() {
|
||||
this.$emit('update:modelValue', this.finalPinCode);
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.init(this.length, this.modelValue);
|
||||
},
|
||||
methods: {
|
||||
init(length, value) {
|
||||
this.codes.length = 0;
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
const code = {
|
||||
value: '',
|
||||
inputType: 'tel',
|
||||
inputTimer: null
|
||||
};
|
||||
|
||||
if (value && value[i]) {
|
||||
code.value = value[i];
|
||||
|
||||
if (this.secure) {
|
||||
code.inputType = 'password';
|
||||
}
|
||||
}
|
||||
|
||||
this.codes.push(code);
|
||||
}
|
||||
},
|
||||
autoFillText(index, text) {
|
||||
let lastIndex = index;
|
||||
|
||||
for (let i = index, j = 0; i < this.codes.length && j < text.length; i++, j++) {
|
||||
if (text[j] < '0' || text[j] > '9') {
|
||||
this.codes[i].value = '';
|
||||
this.$forceUpdate();
|
||||
break;
|
||||
}
|
||||
|
||||
this.codes[i].value = text[j];
|
||||
this.setInputType(i);
|
||||
lastIndex = i;
|
||||
}
|
||||
|
||||
this.setFocus(lastIndex);
|
||||
|
||||
if (this.finalPinCode.length === this.length) {
|
||||
this.$emit('pincode:confirm', this.finalPinCode);
|
||||
}
|
||||
},
|
||||
setInputType(index) {
|
||||
const self = this;
|
||||
|
||||
if (!self.secure) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self.codes[index].value) {
|
||||
self.codes[index].inputType = 'tel';
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.codes[index].inputTimer) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.codes[index].inputTimer = setTimeout(() => {
|
||||
if (self.codes[index].value) {
|
||||
self.codes[index].inputType = 'password';
|
||||
} else {
|
||||
self.codes[index].inputType = 'tel';
|
||||
}
|
||||
|
||||
self.codes[index].inputTimer = null;
|
||||
}, 300);
|
||||
},
|
||||
setFocus(index) {
|
||||
const refId = `pin-code-input-${index}`;
|
||||
const ref = this.$refs[refId];
|
||||
|
||||
if (ref && ref[0]) {
|
||||
ref[0].focus();
|
||||
ref[0].select();
|
||||
}
|
||||
},
|
||||
setPreviousFocus(index) {
|
||||
if (index > 0) {
|
||||
this.setFocus(index - 1);
|
||||
}
|
||||
},
|
||||
setNextFocus(index) {
|
||||
if (index < this.length - 1) {
|
||||
this.setFocus(index + 1);
|
||||
}
|
||||
},
|
||||
onKeydown(index, event) {
|
||||
if (event.code === 'Enter' && this.finalPinCode.length === this.length) {
|
||||
this.$emit('pincode:confirm', this.finalPinCode);
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.code === 'ArrowLeft' || (event.shiftKey && event.code === 'Tab')) {
|
||||
this.setPreviousFocus(index);
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.code === 'ArrowRight' || (!event.shiftKey && event.code === 'Tab')) {
|
||||
this.setNextFocus(index);
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if ((event.ctrlKey || event.metaKey) && event.code === 'KeyV') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.code === 'Backspace' || event.code === 'Delete' || event.code === 'Del') {
|
||||
for (let i = index; i < this.codes.length; i++) {
|
||||
this.codes[i].value = '';
|
||||
this.setInputType(i);
|
||||
}
|
||||
|
||||
if (event.code === 'Backspace') {
|
||||
this.setPreviousFocus(index);
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.code.indexOf('Digit') === 0 && event.code.length === 6) {
|
||||
this.codes[index].value = event.key;
|
||||
this.setInputType(index);
|
||||
this.setNextFocus(index);
|
||||
|
||||
if (this.finalPinCode.length === this.length) {
|
||||
this.$emit('pincode:confirm', this.finalPinCode);
|
||||
}
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
},
|
||||
onPaste(index, event) {
|
||||
if (!event.clipboardData) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
const text = event.clipboardData.getData('Text');
|
||||
|
||||
if (!text) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
this.autoFillText(index, text);
|
||||
|
||||
event.preventDefault();
|
||||
},
|
||||
onInput(index, event) {
|
||||
if (!event.target.value) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
this.autoFillText(index, event.target.value);
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.pin-code-input input {
|
||||
text-align: center;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
height: 60px !important;
|
||||
}
|
||||
|
||||
.pin-code-input.grid.grid-gap {
|
||||
--f7-grid-gap: 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,15 +1,16 @@
|
||||
<template>
|
||||
<f7-sheet style="height:auto" :opened="show"
|
||||
@sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
|
||||
<f7-page-content>
|
||||
<f7-sheet swipe-to-close swipe-handler=".swipe-handler" style="height:auto"
|
||||
:opened="show" @sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
|
||||
<div class="swipe-handler" style="z-index: 10"></div>
|
||||
<f7-page-content class="margin-top no-padding-top">
|
||||
<div class="display-flex padding justify-content-space-between align-items-center">
|
||||
<div style="font-size: 18px"><b>{{ title }}</b></div>
|
||||
</div>
|
||||
<div class="padding-horizontal padding-bottom">
|
||||
<p class="no-margin-top margin-bottom-half">{{ hint }}</p>
|
||||
<f7-list no-hairlines class="no-margin-top margin-bottom">
|
||||
<p class="no-margin">{{ hint }}</p>
|
||||
<f7-list class="no-margin">
|
||||
<f7-list-item class="list-item-pincode-input">
|
||||
<pincode-input secure :length="6" v-model="currentPinCode" />
|
||||
<pin-code-input :secure="true" :length="6" v-model="currentPinCode"/>
|
||||
</f7-list-item>
|
||||
</f7-list>
|
||||
<f7-button large fill
|
||||
@@ -28,13 +29,18 @@
|
||||
<script>
|
||||
export default {
|
||||
props: [
|
||||
'value',
|
||||
'modelValue',
|
||||
'title',
|
||||
'hint',
|
||||
'confirmDisabled',
|
||||
'cancelDisabled',
|
||||
'show'
|
||||
],
|
||||
emits: [
|
||||
'update:modelValue',
|
||||
'update:show',
|
||||
'pincode:confirm'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
currentPinCode: ''
|
||||
@@ -57,7 +63,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit('input', this.currentPinCode);
|
||||
this.$emit('update:modelValue', this.currentPinCode);
|
||||
this.$emit('pincode:confirm', this.currentPinCode);
|
||||
},
|
||||
cancel() {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<f7-sheet :class="{ 'tag-selection-huge-sheet': hugeListItemRows }" :opened="show" @sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
|
||||
<f7-sheet swipe-to-close swipe-handler=".swipe-handler"
|
||||
:opened="show" :class="{ 'tag-selection-huge-sheet': hugeListItemRows }"
|
||||
@sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
|
||||
<f7-toolbar>
|
||||
<div class="swipe-handler"></div>
|
||||
<div class="left">
|
||||
<f7-link sheet-close :text="$t('Cancel')"></f7-link>
|
||||
</div>
|
||||
@@ -9,25 +12,28 @@
|
||||
</div>
|
||||
</f7-toolbar>
|
||||
<f7-page-content>
|
||||
<f7-list no-hairlines class="no-margin-top no-margin-bottom" v-if="!items || !items.length || noAvailableTag">
|
||||
<f7-list class="no-margin-top no-margin-bottom" v-if="!items || !items.length || noAvailableTag">
|
||||
<f7-list-item :title="$t('No available tag')"></f7-list-item>
|
||||
</f7-list>
|
||||
<f7-list no-hairlines class="no-margin-top no-margin-bottom" v-else-if="items && items.length && !noAvailableTag">
|
||||
<f7-list-item checkbox v-for="item in items"
|
||||
v-show="!item.hidden"
|
||||
:key="item.id"
|
||||
:class="item.id | tagItemClass(selectedItemIds)"
|
||||
<f7-list dividers class="no-margin-top no-margin-bottom" v-else-if="items && items.length && !noAvailableTag">
|
||||
<f7-list-item checkbox
|
||||
:class="isChecked(item.id) ? 'list-item-selected' : ''"
|
||||
:value="item.id"
|
||||
:checked="item.id | isChecked(selectedItemIds)"
|
||||
:checked="isChecked(item.id)"
|
||||
:key="item.id"
|
||||
v-for="item in items"
|
||||
v-show="!item.hidden"
|
||||
@change="changeItemSelection">
|
||||
<f7-block slot="title" class="no-padding no-margin">
|
||||
<div class="display-flex">
|
||||
<f7-icon slot="media" f7="number"></f7-icon>
|
||||
<div class="tag-selection-list-item list-item-valign-middle padding-left-half">
|
||||
{{ item.name }}
|
||||
<template #title>
|
||||
<f7-block class="no-padding no-margin">
|
||||
<div class="display-flex">
|
||||
<f7-icon f7="number"></f7-icon>
|
||||
<div class="tag-selection-list-item list-item-valign-middle padding-left-half">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</f7-block>
|
||||
</f7-block>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
</f7-list>
|
||||
</f7-page-content>
|
||||
@@ -37,15 +43,19 @@
|
||||
<script>
|
||||
export default {
|
||||
props: [
|
||||
'value',
|
||||
'modelValue',
|
||||
'items',
|
||||
'show'
|
||||
],
|
||||
emits: [
|
||||
'update:modelValue',
|
||||
'update:show'
|
||||
],
|
||||
data() {
|
||||
const self = this;
|
||||
|
||||
return {
|
||||
selectedItemIds: self.$utilities.copyArrayTo(self.value, [])
|
||||
selectedItemIds: self.$utilities.copyArrayTo(self.modelValue, [])
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -64,11 +74,11 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
save() {
|
||||
this.$emit('input', this.selectedItemIds);
|
||||
this.$emit('update:modelValue', this.selectedItemIds);
|
||||
this.$emit('update:show', false);
|
||||
},
|
||||
onSheetOpen(event) {
|
||||
this.selectedItemIds = this.$utilities.copyArrayTo(this.value, []);
|
||||
this.selectedItemIds = this.$utilities.copyArrayTo(this.modelValue, []);
|
||||
this.scrollToSelectedItem(event.$el);
|
||||
},
|
||||
onSheetClosed() {
|
||||
@@ -95,9 +105,6 @@ export default {
|
||||
}
|
||||
},
|
||||
scrollToSelectedItem(parent) {
|
||||
const app = this.$f7;
|
||||
const $$ = app.$;
|
||||
|
||||
if (!parent || !parent.length) {
|
||||
return;
|
||||
}
|
||||
@@ -113,8 +120,8 @@ export default {
|
||||
let lastSelectedItem = selectedItem;
|
||||
|
||||
if (selectedItem.length > 0) {
|
||||
firstSelectedItem = $$(selectedItem[0]);
|
||||
lastSelectedItem = $$(selectedItem[selectedItem.length - 1]);
|
||||
firstSelectedItem = this.$ui.elements(selectedItem[0]);
|
||||
lastSelectedItem = this.$ui.elements(selectedItem[selectedItem.length - 1]);
|
||||
}
|
||||
|
||||
let firstSelectedItemInTop = firstSelectedItem.offset().top - container.offset().top - parseInt(container.css('padding-top'), 10);
|
||||
@@ -133,26 +140,15 @@ export default {
|
||||
}
|
||||
|
||||
container.scrollTop(targetPos);
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
isChecked(itemId, selectedItemIds) {
|
||||
for (let i = 0; i < selectedItemIds.length; i++) {
|
||||
if (selectedItemIds[i] === itemId) {
|
||||
},
|
||||
isChecked(itemId) {
|
||||
for (let i = 0; i < this.selectedItemIds.length; i++) {
|
||||
if (this.selectedItemIds[i] === itemId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
tagItemClass(itemId, selectedItemIds) {
|
||||
for (let i = 0; i < selectedItemIds.length; i++) {
|
||||
if (selectedItemIds[i] === itemId) {
|
||||
return 'list-item-selected';
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,4 +166,3 @@ export default {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
</style>
|
||||
self
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<template>
|
||||
<f7-sheet :class="{ 'tree-view-selection-huge-sheet': hugeTreeViewItems }" :opened="show" @sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
|
||||
<f7-sheet swipe-to-close swipe-handler=".swipe-handler"
|
||||
:class="{ 'tree-view-selection-huge-sheet': hugeTreeViewItems }"
|
||||
:opened="show" @sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
|
||||
<f7-toolbar>
|
||||
<div class="swipe-handler"></div>
|
||||
<div class="left"></div>
|
||||
<div class="right">
|
||||
<f7-link sheet-close :text="$t('Done')"></f7-link>
|
||||
@@ -8,26 +11,26 @@
|
||||
</f7-toolbar>
|
||||
<f7-page-content>
|
||||
<f7-treeview>
|
||||
<f7-treeview-item v-for="item in items"
|
||||
item-toggle
|
||||
<f7-treeview-item item-toggle
|
||||
:opened="isPrimaryItemHasSecondaryValue(item)"
|
||||
:key="item | itemFieldContent(primaryKeyField, item, false)"
|
||||
:label="item | itemFieldContent(primaryTitleField, item, primaryTitleI18n)">
|
||||
<f7-icon slot="media"
|
||||
:icon="item[primaryIconField] | icon(primaryIconType)"
|
||||
:style="item[primaryColorField] | iconStyle(primaryIconType, 'var(--default-icon-color)')"
|
||||
v-if="primaryIconField"></f7-icon>
|
||||
:label="$tIf((primaryTitleField ? item[primaryTitleField] : item), primaryTitleI18n)"
|
||||
:key="primaryKeyField ? item[primaryKeyField] : item"
|
||||
v-for="item in items">
|
||||
<template #media>
|
||||
<ItemIcon :icon-type="primaryIconType" :icon-id="item[primaryIconField]"
|
||||
:color="item[primaryColorField]" v-if="primaryIconField"></ItemIcon>
|
||||
</template>
|
||||
|
||||
<f7-treeview-item v-for="subItem in item[primarySubItemsField]"
|
||||
selectable
|
||||
<f7-treeview-item selectable
|
||||
:selected="isSecondarySelected(subItem)"
|
||||
:key="subItem | itemFieldContent(secondaryKeyField, subItem, false)"
|
||||
:label="subItem | itemFieldContent(secondaryTitleField, subItem, secondaryTitleI18n)"
|
||||
:label="$tIf((secondaryTitleField ? subItem[secondaryTitleField] : subItem), secondaryTitleI18n)"
|
||||
:key="secondaryKeyField ? subItem[secondaryKeyField] : subItem"
|
||||
v-for="subItem in item[primarySubItemsField]"
|
||||
@click="onSecondaryItemClicked(subItem)">
|
||||
<f7-icon slot="media"
|
||||
:icon="subItem[secondaryIconField] | icon(secondaryIconType)"
|
||||
:style="subItem[secondaryColorField] | iconStyle(secondaryIconType, 'var(--default-icon-color)')"
|
||||
v-if="secondaryIconField"></f7-icon>
|
||||
<template #media>
|
||||
<ItemIcon :icon-type="secondaryIconType" :icon-id="subItem[secondaryIconField]"
|
||||
:color="subItem[secondaryColorField]" v-if="secondaryIconField"></ItemIcon>
|
||||
</template>
|
||||
</f7-treeview-item>
|
||||
</f7-treeview-item>
|
||||
</f7-treeview>
|
||||
@@ -38,7 +41,7 @@
|
||||
<script>
|
||||
export default {
|
||||
props: [
|
||||
'value',
|
||||
'modelValue',
|
||||
'primaryKeyField',
|
||||
'primaryValueField',
|
||||
'primaryTitleField',
|
||||
@@ -57,11 +60,15 @@ export default {
|
||||
'items',
|
||||
'show'
|
||||
],
|
||||
emits: [
|
||||
'update:modelValue',
|
||||
'update:show'
|
||||
],
|
||||
data() {
|
||||
const self = this;
|
||||
|
||||
return {
|
||||
currentValue: self.value
|
||||
currentValue: self.modelValue
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -85,7 +92,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
onSheetOpen(event) {
|
||||
this.currentValue = this.value;
|
||||
this.currentValue = this.modelValue;
|
||||
this.scrollToSelectedItem(event.$el);
|
||||
},
|
||||
onSheetClosed() {
|
||||
@@ -98,7 +105,7 @@ export default {
|
||||
this.currentValue = subItem;
|
||||
}
|
||||
|
||||
this.$emit('input', this.currentValue);
|
||||
this.$emit('update:modelValue', this.currentValue);
|
||||
this.$emit('update:show', false);
|
||||
},
|
||||
isPrimaryItemHasSecondaryValue(primaryItem) {
|
||||
|
||||
@@ -1,56 +1,60 @@
|
||||
<template>
|
||||
<f7-sheet style="height: auto" :opened="show" @sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
|
||||
<f7-sheet swipe-to-close swipe-handler=".swipe-handler"
|
||||
style="height: auto" :opened="show" @sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
|
||||
<f7-toolbar>
|
||||
<div class="swipe-handler"></div>
|
||||
<div class="left"></div>
|
||||
<div class="right">
|
||||
<f7-link sheet-close :text="$t('Done')"></f7-link>
|
||||
</div>
|
||||
</f7-toolbar>
|
||||
<f7-page-content>
|
||||
<f7-row>
|
||||
<f7-col width="50">
|
||||
<div class="grid grid-cols-2 grid-gap">
|
||||
<div>
|
||||
<div class="primary-list-container">
|
||||
<f7-list no-hairlines class="primary-list no-margin-top no-margin-bottom">
|
||||
<f7-list dividers class="primary-list no-margin-vertical">
|
||||
<f7-list-item link="#" no-chevron
|
||||
v-for="item in items"
|
||||
:key="item | itemFieldContent(primaryKeyField, item, false)"
|
||||
:class="{ 'primary-list-item-selected': item === selectedPrimaryItem }"
|
||||
:value="item | itemFieldContent(primaryValueField, item, false)"
|
||||
:title="item | itemFieldContent(primaryTitleField, null, primaryTitleI18n)"
|
||||
:header="item | itemFieldContent(primaryHeaderField, null, primaryHeaderI18n)"
|
||||
:footer="item | itemFieldContent(primaryFooterField, null, primaryFooterI18n)"
|
||||
:value="primaryValueField ? item[primaryValueField] : item"
|
||||
:title="$tIf(item[primaryTitleField], primaryTitleI18n)"
|
||||
:header="$tIf(item[primaryHeaderField], primaryHeaderI18n)"
|
||||
:footer="$tIf(item[primaryFooterField], primaryFooterI18n)"
|
||||
:key="primaryKeyField ? item[primaryKeyField] : item"
|
||||
v-for="item in items"
|
||||
@click="onPrimaryItemClicked(item)">
|
||||
<f7-icon slot="media"
|
||||
:icon="item[primaryIconField] | icon(primaryIconType)"
|
||||
:style="item[primaryColorField] | iconStyle(primaryIconType, 'var(--default-icon-color)')"
|
||||
v-if="primaryIconField"></f7-icon>
|
||||
<f7-icon slot="after" class="list-item-showing" f7="chevron_right" v-if="item === selectedPrimaryItem"></f7-icon>
|
||||
<template #media>
|
||||
<ItemIcon :icon-type="primaryIconType" :icon-id="item[primaryIconField]" :color="item[primaryColorField]"></ItemIcon>
|
||||
</template>
|
||||
<template #after>
|
||||
<f7-icon class="list-item-showing" f7="chevron_right" v-if="item === selectedPrimaryItem"></f7-icon>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
</f7-list>
|
||||
</div>
|
||||
</f7-col>
|
||||
<f7-col width="50">
|
||||
</div>
|
||||
<div>
|
||||
<div class="secondary-list-container">
|
||||
<f7-list no-hairlines class="secondary-list no-margin-top no-margin-bottom" v-if="selectedPrimaryItem && primarySubItemsField && selectedPrimaryItem[primarySubItemsField]">
|
||||
<f7-list dividers class="secondary-list no-margin-vertical" v-if="selectedPrimaryItem && primarySubItemsField && selectedPrimaryItem[primarySubItemsField]">
|
||||
<f7-list-item link="#" no-chevron
|
||||
v-for="subItem in selectedPrimaryItem[primarySubItemsField]"
|
||||
:key="subItem | itemFieldContent(secondaryKeyField, subItem, false)"
|
||||
:class="{ 'secondary-list-item-selected': isSecondarySelected(subItem) }"
|
||||
:value="subItem | itemFieldContent(secondaryValueField, subItem, false)"
|
||||
:title="subItem | itemFieldContent(secondaryTitleField, null, secondaryTitleI18n)"
|
||||
:header="subItem | itemFieldContent(secondaryHeaderField, null, secondaryHeaderI18n)"
|
||||
:footer="subItem | itemFieldContent(secondaryFooterField, null, secondaryFooterI18n)"
|
||||
:value="secondaryValueField ? subItem[secondaryValueField] : subItem"
|
||||
:title="$tIf(subItem[secondaryTitleField], secondaryTitleI18n)"
|
||||
:header="$tIf(subItem[secondaryHeaderField], secondaryHeaderI18n)"
|
||||
:footer="$tIf(subItem[secondaryFooterField], secondaryFooterI18n)"
|
||||
:key="secondaryKeyField ? subItem[secondaryKeyField] : subItem"
|
||||
v-for="subItem in selectedPrimaryItem[primarySubItemsField]"
|
||||
@click="onSecondaryItemClicked(subItem)">
|
||||
<f7-icon slot="media"
|
||||
:icon="subItem[secondaryIconField] | icon(secondaryIconType)"
|
||||
:style="subItem[secondaryColorField] | iconStyle(secondaryIconType, 'var(--default-icon-color)')"
|
||||
v-if="secondaryIconField"></f7-icon>
|
||||
<f7-icon slot="after" class="list-item-checked-icon" f7="checkmark_alt" v-if="isSecondarySelected(subItem)"></f7-icon>
|
||||
<template #media>
|
||||
<ItemIcon :icon-type="secondaryIconType" :icon-id="subItem[secondaryIconField]" :color="subItem[secondaryColorField]"></ItemIcon>
|
||||
</template>
|
||||
<template #after>
|
||||
<f7-icon class="list-item-checked-icon" f7="checkmark_alt" v-if="isSecondarySelected(subItem)"></f7-icon>
|
||||
</template>
|
||||
</f7-list-item>
|
||||
</f7-list>
|
||||
</div>
|
||||
</f7-col>
|
||||
</f7-row>
|
||||
</div>
|
||||
</div>
|
||||
</f7-page-content>
|
||||
</f7-sheet>
|
||||
</template>
|
||||
@@ -58,7 +62,7 @@
|
||||
<script>
|
||||
export default {
|
||||
props: [
|
||||
'value',
|
||||
'modelValue',
|
||||
'primaryKeyField',
|
||||
'primaryValueField',
|
||||
'primaryTitleField',
|
||||
@@ -85,12 +89,16 @@ export default {
|
||||
'items',
|
||||
'show'
|
||||
],
|
||||
emits: [
|
||||
'update:modelValue',
|
||||
'update:show'
|
||||
],
|
||||
data() {
|
||||
const self = this;
|
||||
|
||||
return {
|
||||
currentPrimaryValue: self.getPrimaryValueBySecondaryValue(self.value),
|
||||
currentSecondaryValue: self.value
|
||||
currentPrimaryValue: self.getPrimaryValueBySecondaryValue(self.modelValue),
|
||||
currentSecondaryValue: self.modelValue
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -126,13 +134,13 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
onSheetOpen(event) {
|
||||
this.currentPrimaryValue = this.getPrimaryValueBySecondaryValue(this.value);
|
||||
this.currentSecondaryValue = this.value;
|
||||
this.currentPrimaryValue = this.getPrimaryValueBySecondaryValue(this.modelValue);
|
||||
this.currentSecondaryValue = this.modelValue;
|
||||
this.scrollToSelectedItem(event.$el, '.primary-list-container', 'li.primary-list-item-selected');
|
||||
this.scrollToSelectedItem(event.$el, '.secondary-list-container', 'li.secondary-list-item-selected');
|
||||
},
|
||||
onSheetClosed() {
|
||||
this.$emit('update:show', false);
|
||||
this.close();
|
||||
},
|
||||
onPrimaryItemClicked(item) {
|
||||
if (this.primaryValueField) {
|
||||
@@ -148,8 +156,8 @@ export default {
|
||||
this.currentSecondaryValue = subItem;
|
||||
}
|
||||
|
||||
this.$emit('input', this.currentSecondaryValue);
|
||||
this.$emit('update:show', false);
|
||||
this.$emit('update:modelValue', this.currentSecondaryValue);
|
||||
this.close();
|
||||
},
|
||||
isSecondarySelected(subItem) {
|
||||
if (this.secondaryValueField) {
|
||||
@@ -226,6 +234,9 @@ export default {
|
||||
}
|
||||
|
||||
container.scrollTop(targetPos);
|
||||
},
|
||||
close() {
|
||||
this.$emit('update:show', false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -1,5 +1,7 @@
|
||||
const baseUrlPath = '/api';
|
||||
const baseApiUrlPath = '/api';
|
||||
const baseProxyUrlPath = '/proxy';
|
||||
|
||||
export default {
|
||||
baseUrlPath: baseUrlPath
|
||||
baseApiUrlPath: baseApiUrlPath,
|
||||
baseProxyUrlPath: baseProxyUrlPath
|
||||
}
|
||||
|
||||
+14
-2
@@ -495,7 +495,11 @@ const allCurrencies = {
|
||||
code: 'SHP',
|
||||
symbol: '£'
|
||||
},
|
||||
'SLL': { // Leone
|
||||
'SLE': { // Leone (new leone)
|
||||
code: 'SLE',
|
||||
symbol: 'Le'
|
||||
},
|
||||
'SLL': { // Leone (old leone)
|
||||
code: 'SLL',
|
||||
symbol: 'Le'
|
||||
},
|
||||
@@ -582,9 +586,17 @@ const allCurrencies = {
|
||||
'UZS': { // Uzbekistan Sum
|
||||
code: 'UZS'
|
||||
},
|
||||
'VED': { // Bolívar Digital
|
||||
code: 'VED',
|
||||
symbol: 'Bs.D'
|
||||
},
|
||||
'VEF': { // Bolívar Fuerte
|
||||
code: 'VEF',
|
||||
symbol: 'Bs.F'
|
||||
},
|
||||
'VES': { // Bolívar Soberano
|
||||
code: 'VES',
|
||||
symbol: 'Bs.S.'
|
||||
symbol: 'Bs.S'
|
||||
},
|
||||
'VND': { // Dong
|
||||
code: 'VND',
|
||||
|
||||
+109
-1
@@ -39,6 +39,96 @@ const allWeekDaysArray = [
|
||||
allWeekDays.Saturday
|
||||
];
|
||||
|
||||
const allLongDateFormat = {
|
||||
YYYYMMDD: {
|
||||
type: 1,
|
||||
key: 'yyyy_mm_dd'
|
||||
},
|
||||
MMDDYYYY: {
|
||||
type: 2,
|
||||
key: 'mm_dd_yyyy'
|
||||
},
|
||||
DDMMYYYY: {
|
||||
type: 3,
|
||||
key: 'dd_mm_yyyy'
|
||||
}
|
||||
};
|
||||
|
||||
const allLongDateFormatArray = [
|
||||
allLongDateFormat.YYYYMMDD,
|
||||
allLongDateFormat.MMDDYYYY,
|
||||
allLongDateFormat.DDMMYYYY
|
||||
];
|
||||
|
||||
const allShortDateFormat = {
|
||||
YYYYMMDD: {
|
||||
type: 1,
|
||||
key: 'yyyy_mm_dd'
|
||||
},
|
||||
MMDDYYYY: {
|
||||
type: 2,
|
||||
key: 'mm_dd_yyyy'
|
||||
},
|
||||
DDMMYYYY: {
|
||||
type: 3,
|
||||
key: 'dd_mm_yyyy'
|
||||
}
|
||||
};
|
||||
|
||||
const allShortDateFormatArray = [
|
||||
allShortDateFormat.YYYYMMDD,
|
||||
allShortDateFormat.MMDDYYYY,
|
||||
allShortDateFormat.DDMMYYYY
|
||||
];
|
||||
|
||||
const allLongTimeFormat = {
|
||||
HHMMSS: {
|
||||
type: 1,
|
||||
key: 'hh_mm_ss',
|
||||
is24HourFormat: true
|
||||
},
|
||||
AHHMMSS: {
|
||||
type: 2,
|
||||
key: 'a_hh_mm_ss',
|
||||
is24HourFormat: false
|
||||
},
|
||||
HHMMSSA: {
|
||||
type: 3,
|
||||
key: 'hh_mm_ss_a',
|
||||
is24HourFormat: false
|
||||
}
|
||||
};
|
||||
|
||||
const allLongTimeFormatArray = [
|
||||
allLongTimeFormat.HHMMSS,
|
||||
allLongTimeFormat.AHHMMSS,
|
||||
allLongTimeFormat.HHMMSSA
|
||||
];
|
||||
|
||||
const allShortTimeFormat = {
|
||||
HHMM: {
|
||||
type: 1,
|
||||
key: 'hh_mm',
|
||||
is24HourFormat: true
|
||||
},
|
||||
AHHMM: {
|
||||
type: 2,
|
||||
key: 'a_hh_mm',
|
||||
is24HourFormat: false
|
||||
},
|
||||
HHMMA: {
|
||||
type: 3,
|
||||
key: 'hh_mm_a',
|
||||
is24HourFormat: false
|
||||
}
|
||||
};
|
||||
|
||||
const allShortTimeFormatArray = [
|
||||
allShortTimeFormat.HHMM,
|
||||
allShortTimeFormat.AHHMM,
|
||||
allShortTimeFormat.HHMMA
|
||||
];
|
||||
|
||||
const allDateRanges = {
|
||||
All: {
|
||||
type: 0,
|
||||
@@ -91,10 +181,28 @@ const allDateRanges = {
|
||||
};
|
||||
|
||||
const defaultFirstDayOfWeek = allWeekDays.Sunday.type;
|
||||
const defaultLongDateFormat = allLongDateFormat.YYYYMMDD;
|
||||
const defaultShortDateFormat = allShortDateFormat.YYYYMMDD;
|
||||
const defaultLongTimeFormat = allLongTimeFormat.HHMMSS;
|
||||
const defaultShortTimeFormat = allShortTimeFormat.HHMM;
|
||||
const defaultDateTimeFormatValue = 0;
|
||||
|
||||
export default {
|
||||
allWeekDays: allWeekDays,
|
||||
allWeekDaysArray: allWeekDaysArray,
|
||||
allLongDateFormat: allLongDateFormat,
|
||||
allLongDateFormatArray: allLongDateFormatArray,
|
||||
allShortDateFormat: allShortDateFormat,
|
||||
allShortDateFormatArray: allShortDateFormatArray,
|
||||
allLongTimeFormat: allLongTimeFormat,
|
||||
allLongTimeFormatArray: allLongTimeFormatArray,
|
||||
allShortTimeFormat: allShortTimeFormat,
|
||||
allShortTimeFormatArray: allShortTimeFormatArray,
|
||||
allDateRanges: allDateRanges,
|
||||
defaultFirstDayOfWeek: defaultFirstDayOfWeek
|
||||
defaultFirstDayOfWeek: defaultFirstDayOfWeek,
|
||||
defaultLongDateFormat: defaultLongDateFormat,
|
||||
defaultShortDateFormat: defaultShortDateFormat,
|
||||
defaultLongTimeFormat: defaultLongTimeFormat,
|
||||
defaultShortTimeFormat: defaultShortTimeFormat,
|
||||
defaultDateTimeFormatValue: defaultDateTimeFormatValue,
|
||||
};
|
||||
|
||||
@@ -256,6 +256,9 @@ const allCategoryIcons = {
|
||||
'211': {
|
||||
icon: 'las la-umbrella'
|
||||
},
|
||||
'212': {
|
||||
icon: 'las la-baby-carriage'
|
||||
},
|
||||
'220': {
|
||||
icon: 'las la-couch'
|
||||
},
|
||||
@@ -436,6 +439,9 @@ const allCategoryIcons = {
|
||||
'517': {
|
||||
icon: 'las la-snowboarding'
|
||||
},
|
||||
'518': {
|
||||
icon: 'las la-hiking'
|
||||
},
|
||||
'520': {
|
||||
icon: 'las la-futbol'
|
||||
},
|
||||
@@ -508,6 +514,9 @@ const allCategoryIcons = {
|
||||
'570': {
|
||||
icon: 'las la-id-card-alt'
|
||||
},
|
||||
'571': {
|
||||
icon: 'las la-podcast'
|
||||
},
|
||||
'580': {
|
||||
icon: 'las la-dog'
|
||||
},
|
||||
@@ -582,6 +591,9 @@ const allCategoryIcons = {
|
||||
'720': {
|
||||
icon: 'las la-birthday-cake'
|
||||
},
|
||||
'760': {
|
||||
icon: 'las la-ribbon'
|
||||
},
|
||||
'780': {
|
||||
icon: 'las la-donate'
|
||||
},
|
||||
|
||||
@@ -593,5 +593,6 @@ const allAvailableTimezones = [
|
||||
];
|
||||
|
||||
export default {
|
||||
all: allAvailableTimezones
|
||||
all: allAvailableTimezones,
|
||||
utcTimezoneName: 'Etc/GMT'
|
||||
};
|
||||
|
||||
+4
-6
@@ -1,8 +1,6 @@
|
||||
import Vue from 'vue';
|
||||
import { createApp } from 'vue'
|
||||
|
||||
import App from './Desktop.vue';
|
||||
import App from './DesktopApp.vue';
|
||||
|
||||
new Vue({
|
||||
el: '#app',
|
||||
render: h => h(App),
|
||||
})
|
||||
const app = createApp(App);
|
||||
app.mount('#app');
|
||||
|
||||
@@ -10,9 +10,11 @@
|
||||
<title>ezBookkeeping</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but ezBookkeeping doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<noscript>
|
||||
<strong>We're sorry but ezBookkeeping doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
|
||||
<script type="module" src="./desktop-main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,10 @@
|
||||
import { autoChangeTextareaSize } from '../../lib/mobile/ui.js';
|
||||
|
||||
export default {
|
||||
mounted(el) {
|
||||
autoChangeTextareaSize(el);
|
||||
},
|
||||
updated(el) {
|
||||
autoChangeTextareaSize(el);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import icons from '../consts/icon.js';
|
||||
import utils from '../lib/utils.js';
|
||||
|
||||
export default function (iconId) {
|
||||
if (utils.isNumber(iconId)) {
|
||||
iconId = iconId.toString();
|
||||
}
|
||||
|
||||
if (!icons.allAccountIcons[iconId]) {
|
||||
return icons.defaultAccountIcon.icon;
|
||||
}
|
||||
|
||||
return icons.allAccountIcons[iconId].icon;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import colorConstants from '../consts/color.js';
|
||||
|
||||
export default function (color, defaultColor, additionalFieldName) {
|
||||
if (color && color !== colorConstants.defaultAccountColor) {
|
||||
color = '#' + color;
|
||||
} else {
|
||||
color = defaultColor;
|
||||
}
|
||||
|
||||
const ret = {
|
||||
color: color
|
||||
};
|
||||
|
||||
if (additionalFieldName) {
|
||||
ret[additionalFieldName] = color;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import icons from '../consts/icon.js';
|
||||
import utils from '../lib/utils.js';
|
||||
|
||||
export default function (iconId) {
|
||||
if (utils.isNumber(iconId)) {
|
||||
iconId = iconId.toString();
|
||||
}
|
||||
|
||||
if (!icons.allCategoryIcons[iconId]) {
|
||||
return icons.defaultCategoryIcon.icon;
|
||||
}
|
||||
|
||||
return icons.allCategoryIcons[iconId].icon;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import colorConstants from '../consts/color.js';
|
||||
|
||||
export default function (color, defaultColor, additionalFieldName) {
|
||||
if (color && color !== colorConstants.defaultCategoryColor) {
|
||||
color = '#' + color;
|
||||
} else {
|
||||
color = defaultColor;
|
||||
}
|
||||
|
||||
const ret = {
|
||||
color: color
|
||||
};
|
||||
|
||||
if (additionalFieldName) {
|
||||
ret[additionalFieldName] = color;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import currency from '../consts/currency.js';
|
||||
import settings from '../lib/settings.js';
|
||||
import utils from '../lib/utils.js';
|
||||
|
||||
export default function ({i18n}, value, currencyCode, notConvertValue) {
|
||||
if (!utils.isNumber(value) && !utils.isString(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (utils.isNumber(value)) {
|
||||
value = value.toString();
|
||||
}
|
||||
|
||||
if (!notConvertValue) {
|
||||
const hasIncompleteFlag = utils.isString(value) && value.charAt(value.length - 1) === '+';
|
||||
|
||||
if (hasIncompleteFlag) {
|
||||
value = value.substr(0, value.length - 1);
|
||||
}
|
||||
|
||||
value = utils.numericCurrencyToString(value);
|
||||
|
||||
if (hasIncompleteFlag) {
|
||||
value = value + '+';
|
||||
}
|
||||
}
|
||||
|
||||
const currencyDisplayMode = settings.getCurrencyDisplayMode();
|
||||
|
||||
if (currencyCode && currencyDisplayMode === currency.allCurrencyDisplayModes.Symbol) {
|
||||
const currencyInfo = currency.all[currencyCode];
|
||||
let currencySymbol = currency.defaultCurrencySymbol;
|
||||
|
||||
if (currencyInfo && currencyInfo.symbol) {
|
||||
currencySymbol = currencyInfo.symbol;
|
||||
} else if (currencyInfo && currencyInfo.code) {
|
||||
currencySymbol = currencyInfo.code;
|
||||
}
|
||||
|
||||
return i18n.t('format.currency.symbol', {
|
||||
amount: value,
|
||||
symbol: currencySymbol
|
||||
});
|
||||
} else if (currencyCode && currencyDisplayMode === currency.allCurrencyDisplayModes.Code) {
|
||||
return `${value} ${currencyCode}`;
|
||||
} else if (currencyCode && currencyDisplayMode === currency.allCurrencyDisplayModes.Name) {
|
||||
const currencyName = i18n.t(`currency.${currencyCode}`);
|
||||
return `${value} ${currencyName}`;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import colorConstants from '../consts/color.js';
|
||||
|
||||
export default function (color, defaultColor) {
|
||||
if (color && color !== colorConstants.defaultColor) {
|
||||
color = '#' + color;
|
||||
} else {
|
||||
color = defaultColor;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import colorConstants from '../consts/color.js';
|
||||
|
||||
export default function (color, defaultColor, additionalFieldName) {
|
||||
if (color && color !== colorConstants.defaultColor) {
|
||||
color = '#' + color;
|
||||
} else {
|
||||
color = defaultColor;
|
||||
}
|
||||
|
||||
const ret = {
|
||||
color: color
|
||||
};
|
||||
|
||||
if (additionalFieldName) {
|
||||
ret[additionalFieldName] = color;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
export default function (rate) {
|
||||
const rateStr = rate.toString();
|
||||
|
||||
if (rateStr.indexOf('.') < 0) {
|
||||
return rateStr;
|
||||
} else {
|
||||
let firstNonZeroPos = 0;
|
||||
|
||||
for (let i = 0; i < rateStr.length; i++) {
|
||||
if (rateStr.charAt(i) !== '.' && rateStr.charAt(i) !== '0') {
|
||||
firstNonZeroPos = Math.min(i + 4, rateStr.length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return rateStr.substr(0, Math.max(6, Math.max(firstNonZeroPos, rateStr.indexOf('.') + 2)));
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export default function (value, format) {
|
||||
return format.replaceAll(/#{value}/g, value);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import accountIcon from './accountIcon.js';
|
||||
import categoryIcon from './categoryIcon.js';
|
||||
|
||||
export default function (iconId, iconType) {
|
||||
if (iconType === 'account') {
|
||||
return accountIcon(iconId);
|
||||
} else if (iconType === 'category') {
|
||||
return categoryIcon(iconId);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import defaultIconStyle from './defaultIconStyle.js';
|
||||
import accountIconStyle from './accountIconStyle.js';
|
||||
import categoryIconStyle from './categoryIconStyle.js';
|
||||
|
||||
export default function (color, iconType, defaultColor, additionalFieldName) {
|
||||
if (iconType === 'account') {
|
||||
return accountIconStyle(color, defaultColor, additionalFieldName);
|
||||
} else if (iconType === 'category') {
|
||||
return categoryIconStyle(color, defaultColor, additionalFieldName);
|
||||
} else {
|
||||
return defaultIconStyle(color, defaultColor, additionalFieldName);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
export default function ({i18n}, value, fieldName, defaultValue, translate) {
|
||||
let content = defaultValue;
|
||||
|
||||
if (fieldName) {
|
||||
content = value[fieldName];
|
||||
}
|
||||
|
||||
if (translate && content) {
|
||||
content = i18n.t(content);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { allLanguages } from '../locales/index.js';
|
||||
|
||||
export default function (languageCode) {
|
||||
const lang = allLanguages[languageCode];
|
||||
|
||||
if (!lang) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return lang.displayName;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export default function ({i18n}, text, options) {
|
||||
return i18n.t(text, options || {});
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import utils from '../lib/utils.js';
|
||||
|
||||
export default function (value, format, options) {
|
||||
if (!utils.isNumber(value)) {
|
||||
value = utils.getUnixTime(value);
|
||||
}
|
||||
|
||||
let utcOffset = null;
|
||||
let currentUtcOffset = null;
|
||||
|
||||
if (utils.isObject(options) && utils.isNumber(options.utcOffset)) {
|
||||
utcOffset = options.utcOffset;
|
||||
}
|
||||
|
||||
if (utils.isObject(options) && utils.isNumber(options.currentUtcOffset)) {
|
||||
currentUtcOffset = options.currentUtcOffset;
|
||||
}
|
||||
|
||||
return utils.formatUnixTime(value, format, utcOffset, currentUtcOffset);
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import utils from '../lib/utils.js';
|
||||
|
||||
export default function (value, options, keyField, nameField, defaultName) {
|
||||
if (utils.isArray(options)) {
|
||||
if (keyField) {
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
const option = options[i];
|
||||
|
||||
if (option[keyField] === value) {
|
||||
return option[nameField];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (options[value]) {
|
||||
const option = options[value];
|
||||
|
||||
return option[nameField];
|
||||
}
|
||||
}
|
||||
} else if (utils.isObject(options)) {
|
||||
if (keyField) {
|
||||
for (let key in options) {
|
||||
if (!Object.prototype.hasOwnProperty.call(options, key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const option = options[key];
|
||||
|
||||
if (option[keyField] === value) {
|
||||
return option[nameField];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (options[value]) {
|
||||
const option = options[value];
|
||||
|
||||
return option[nameField];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return defaultName;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
export default function (value, precision, lowPrecisionValue) {
|
||||
const ratio = Math.pow(10, precision);
|
||||
const normalizedValue = Math.floor(value * ratio);
|
||||
|
||||
if (value > 0 && normalizedValue < 1 && lowPrecisionValue) {
|
||||
return lowPrecisionValue + '%';
|
||||
}
|
||||
|
||||
const result = normalizedValue / ratio;
|
||||
return result + '%';
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
export default function (value, maxLength) {
|
||||
let length = 0;
|
||||
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
const c = value.charCodeAt(i);
|
||||
|
||||
if ((c >= 0x0001 && c <= 0x007e) || (0xff60 <= c && c <= 0xff9f)) {
|
||||
length++;
|
||||
} else {
|
||||
length += 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (length <= maxLength || maxLength <= 3) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return value.substr(0, maxLength - 3) + '...';
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import utils from '../lib/utils.js';
|
||||
|
||||
export default function (token) {
|
||||
const ua = utils.parseUserAgent(token.userAgent);
|
||||
let result = '';
|
||||
|
||||
if (ua.device.model) {
|
||||
result = ua.device.model;
|
||||
} else if (ua.os.name) {
|
||||
result = ua.os.name;
|
||||
|
||||
if (ua.os.version) {
|
||||
result += ' ' + ua.os.version;
|
||||
}
|
||||
}
|
||||
|
||||
if (ua.browser.name) {
|
||||
let browserInfo = ua.browser.name;
|
||||
|
||||
if (ua.browser.version) {
|
||||
browserInfo += ' ' + ua.browser.version;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
result += ' (' + browserInfo + ')';
|
||||
} else {
|
||||
result = browserInfo;
|
||||
}
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
return 'Unknown Device';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import icons from '../consts/icon.js';
|
||||
import utils from '../lib/utils.js';
|
||||
|
||||
export default function (token) {
|
||||
const ua = utils.parseUserAgent(token.userAgent);
|
||||
|
||||
if (!ua || !ua.device) {
|
||||
return icons.deviceIcons.desktop.f7Icon;
|
||||
}
|
||||
|
||||
if (ua.device.type === 'mobile') {
|
||||
return icons.deviceIcons.mobile.f7Icon;
|
||||
} else if (ua.device.type === 'wearable') {
|
||||
return icons.deviceIcons.wearable.f7Icon;
|
||||
} else if (ua.device.type === 'tablet') {
|
||||
return icons.deviceIcons.tablet.f7Icon;
|
||||
} else if (ua.device.type === 'smarttv') {
|
||||
return icons.deviceIcons.tv.f7Icon;
|
||||
} else {
|
||||
return icons.deviceIcons.desktop.f7Icon;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import utils from '../lib/utils.js';
|
||||
|
||||
export default function (utcOffsetMinutes) {
|
||||
const utcOffset = utils.getUtcOffsetByUtcOffsetMinutes(utcOffsetMinutes);
|
||||
return `(UTC${utcOffset})`;
|
||||
}
|
||||
@@ -52,9 +52,11 @@
|
||||
<link rel="manifest" href="manifest.json">
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but ezBookkeeping doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<noscript>
|
||||
<strong>We're sorry but ezBookkeeping doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
|
||||
<script type="module" src="./index-main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+317
-2
@@ -1,4 +1,9 @@
|
||||
import { defaultLanguage, allLanguages } from '../locales/index.js';
|
||||
import datetime from "../consts/datetime.js";
|
||||
import timezone from "../consts/timezone.js";
|
||||
import currency from "../consts/currency.js";
|
||||
import settings from "./settings.js";
|
||||
import utilities from './utilities/index.js';
|
||||
|
||||
const apiNotFoundErrorCode = 100001;
|
||||
const specifiedApiNotFoundErrors = {
|
||||
@@ -121,11 +126,11 @@ const parameterizedErrors = [
|
||||
}
|
||||
];
|
||||
|
||||
export function getAllLanguages() {
|
||||
export function getAllLanguageInfos() {
|
||||
return allLanguages;
|
||||
}
|
||||
|
||||
export function getLanguage(locale) {
|
||||
export function getLanguageInfo(locale) {
|
||||
return allLanguages[locale];
|
||||
}
|
||||
|
||||
@@ -159,6 +164,15 @@ export function getDefaultLanguage() {
|
||||
browserLocale = locale;
|
||||
}
|
||||
}
|
||||
|
||||
if (!allLanguages[browserLocale]) {
|
||||
browserLocale = localeParts[0];
|
||||
const locale = getLocaleFromLanguageAlias(browserLocale);
|
||||
|
||||
if (locale) {
|
||||
browserLocale = locale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!allLanguages[browserLocale]) {
|
||||
@@ -168,8 +182,278 @@ export function getDefaultLanguage() {
|
||||
return browserLocale;
|
||||
}
|
||||
|
||||
export function transateIf(text, isTranslate, translateFn) {
|
||||
if (isTranslate) {
|
||||
return translateFn(text);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
export function getAllLongMonthNames(translateFn) {
|
||||
return [
|
||||
translateFn('datetime.January.long'),
|
||||
translateFn('datetime.February.long'),
|
||||
translateFn('datetime.March.long'),
|
||||
translateFn('datetime.April.long'),
|
||||
translateFn('datetime.May.long'),
|
||||
translateFn('datetime.June.long'),
|
||||
translateFn('datetime.July.long'),
|
||||
translateFn('datetime.August.long'),
|
||||
translateFn('datetime.September.long'),
|
||||
translateFn('datetime.October.long'),
|
||||
translateFn('datetime.November.long'),
|
||||
translateFn('datetime.December.long')
|
||||
];
|
||||
}
|
||||
|
||||
export function getAllShortMonthNames(translateFn) {
|
||||
return [
|
||||
translateFn('datetime.January.short'),
|
||||
translateFn('datetime.February.short'),
|
||||
translateFn('datetime.March.short'),
|
||||
translateFn('datetime.April.short'),
|
||||
translateFn('datetime.May.short'),
|
||||
translateFn('datetime.June.short'),
|
||||
translateFn('datetime.July.short'),
|
||||
translateFn('datetime.August.short'),
|
||||
translateFn('datetime.September.short'),
|
||||
translateFn('datetime.October.short'),
|
||||
translateFn('datetime.November.short'),
|
||||
translateFn('datetime.December.short')
|
||||
];
|
||||
}
|
||||
|
||||
export function getAllLongWeekdayNames(translateFn) {
|
||||
return [
|
||||
translateFn('datetime.Sunday.long'),
|
||||
translateFn('datetime.Monday.long'),
|
||||
translateFn('datetime.Tuesday.long'),
|
||||
translateFn('datetime.Wednesday.long'),
|
||||
translateFn('datetime.Thursday.long'),
|
||||
translateFn('datetime.Friday.long'),
|
||||
translateFn('datetime.Saturday.long')
|
||||
];
|
||||
}
|
||||
|
||||
export function getAllShortWeekdayNames(translateFn) {
|
||||
return [
|
||||
translateFn('datetime.Sunday.short'),
|
||||
translateFn('datetime.Monday.short'),
|
||||
translateFn('datetime.Tuesday.short'),
|
||||
translateFn('datetime.Wednesday.short'),
|
||||
translateFn('datetime.Thursday.short'),
|
||||
translateFn('datetime.Friday.short'),
|
||||
translateFn('datetime.Saturday.short')
|
||||
];
|
||||
}
|
||||
|
||||
export function getAllMinWeekdayNames(translateFn) {
|
||||
return [
|
||||
translateFn('datetime.Sunday.min'),
|
||||
translateFn('datetime.Monday.min'),
|
||||
translateFn('datetime.Tuesday.min'),
|
||||
translateFn('datetime.Wednesday.min'),
|
||||
translateFn('datetime.Thursday.min'),
|
||||
translateFn('datetime.Friday.min'),
|
||||
translateFn('datetime.Saturday.min')
|
||||
];
|
||||
}
|
||||
|
||||
export function getAllLongDateFormats(translateFn) {
|
||||
const defaultLongDateFormatTypeName = translateFn('default.longDateFormat');
|
||||
return getDateTimeFormats(translateFn, datetime.allLongDateFormat, datetime.allLongDateFormatArray, 'format.longDate', defaultLongDateFormatTypeName, datetime.defaultLongDateFormat);
|
||||
}
|
||||
|
||||
export function getAllShortDateFormats(translateFn) {
|
||||
const defaultShortDateFormatTypeName = translateFn('default.shortDateFormat');
|
||||
return getDateTimeFormats(translateFn, datetime.allShortDateFormat, datetime.allShortDateFormatArray, 'format.shortDate', defaultShortDateFormatTypeName, datetime.defaultShortDateFormat);
|
||||
}
|
||||
|
||||
export function getAllLongTimeFormats(translateFn) {
|
||||
const defaultLongTimeFormatTypeName = translateFn('default.longTimeFormat');
|
||||
return getDateTimeFormats(translateFn, datetime.allLongTimeFormat, datetime.allLongTimeFormatArray, 'format.longTime', defaultLongTimeFormatTypeName, datetime.defaultLongTimeFormat);
|
||||
}
|
||||
|
||||
export function getAllShortTimeFormats(translateFn) {
|
||||
const defaultShortTimeFormatTypeName = translateFn('default.shortTimeFormat');
|
||||
return getDateTimeFormats(translateFn, datetime.allShortTimeFormat, datetime.allShortTimeFormatArray, 'format.shortTime', defaultShortTimeFormatTypeName, datetime.defaultShortTimeFormat);
|
||||
}
|
||||
|
||||
export function getI18nLongDateFormat(translateFn, formatTypeValue) {
|
||||
const defaultLongDateFormatTypeName = translateFn('default.longDateFormat');
|
||||
return getDateTimeFormat(translateFn, datetime.allLongDateFormat, datetime.allLongDateFormatArray, 'format.longDate', defaultLongDateFormatTypeName, datetime.defaultLongDateFormat, formatTypeValue);
|
||||
}
|
||||
|
||||
export function getI18nShortDateFormat(translateFn, formatTypeValue) {
|
||||
const defaultShortDateFormatTypeName = translateFn('default.shortDateFormat');
|
||||
return getDateTimeFormat(translateFn, datetime.allShortDateFormat, datetime.allShortDateFormatArray, 'format.shortDate', defaultShortDateFormatTypeName, datetime.defaultShortDateFormat, formatTypeValue);
|
||||
}
|
||||
|
||||
export function getI18nLongYearFormat(translateFn, formatTypeValue) {
|
||||
const defaultLongDateFormatTypeName = translateFn('default.longDateFormat');
|
||||
return getDateTimeFormat(translateFn, datetime.allLongDateFormat, datetime.allLongDateFormatArray, 'format.longYear', defaultLongDateFormatTypeName, datetime.defaultLongDateFormat, formatTypeValue);
|
||||
}
|
||||
|
||||
export function getI18nShortYearFormat(translateFn, formatTypeValue) {
|
||||
const defaultShortDateFormatTypeName = translateFn('default.shortDateFormat');
|
||||
return getDateTimeFormat(translateFn, datetime.allShortDateFormat, datetime.allShortDateFormatArray, 'format.shortYear', defaultShortDateFormatTypeName, datetime.defaultShortDateFormat, formatTypeValue);
|
||||
}
|
||||
|
||||
export function getI18nLongYearMonthFormat(translateFn, formatTypeValue) {
|
||||
const defaultLongDateFormatTypeName = translateFn('default.longDateFormat');
|
||||
return getDateTimeFormat(translateFn, datetime.allLongDateFormat, datetime.allLongDateFormatArray, 'format.longYearMonth', defaultLongDateFormatTypeName, datetime.defaultLongDateFormat, formatTypeValue);
|
||||
}
|
||||
|
||||
export function getI18nShortYearMonthFormat(translateFn, formatTypeValue) {
|
||||
const defaultShortDateFormatTypeName = translateFn('default.shortDateFormat');
|
||||
return getDateTimeFormat(translateFn, datetime.allShortDateFormat, datetime.allShortDateFormatArray, 'format.shortYearMonth', defaultShortDateFormatTypeName, datetime.defaultShortDateFormat, formatTypeValue);
|
||||
}
|
||||
|
||||
export function getI18nLongMonthDayFormat(translateFn, formatTypeValue) {
|
||||
const defaultLongDateFormatTypeName = translateFn('default.longDateFormat');
|
||||
return getDateTimeFormat(translateFn, datetime.allLongDateFormat, datetime.allLongDateFormatArray, 'format.longMonthDay', defaultLongDateFormatTypeName, datetime.defaultLongDateFormat, formatTypeValue);
|
||||
}
|
||||
|
||||
export function getI18nShortMonthDayFormat(translateFn, formatTypeValue) {
|
||||
const defaultShortDateFormatTypeName = translateFn('default.shortDateFormat');
|
||||
return getDateTimeFormat(translateFn, datetime.allShortDateFormat, datetime.allShortDateFormatArray, 'format.shortMonthDay', defaultShortDateFormatTypeName, datetime.defaultShortDateFormat, formatTypeValue);
|
||||
}
|
||||
|
||||
export function getI18nLongTimeFormat(translateFn, formatTypeValue) {
|
||||
const defaultLongTimeFormatTypeName = translateFn('default.longTimeFormat');
|
||||
return getDateTimeFormat(translateFn, datetime.allLongTimeFormat, datetime.allLongTimeFormatArray, 'format.longTime', defaultLongTimeFormatTypeName, datetime.defaultLongTimeFormat, formatTypeValue);
|
||||
}
|
||||
|
||||
export function getI18nShortTimeFormat(translateFn, formatTypeValue) {
|
||||
const defaultShortTimeFormatTypeName = translateFn('default.shortTimeFormat');
|
||||
return getDateTimeFormat(translateFn, datetime.allShortTimeFormat, datetime.allShortTimeFormatArray, 'format.shortTime', defaultShortTimeFormatTypeName, datetime.defaultShortTimeFormat, formatTypeValue);
|
||||
}
|
||||
|
||||
export function isLongTime24HourFormat(translateFn, formatTypeValue) {
|
||||
const defaultLongTimeFormatTypeName = translateFn('default.longTimeFormat');
|
||||
const type = utilities.getDateTimeFormatType(datetime.allLongTimeFormat, datetime.allLongTimeFormatArray, defaultLongTimeFormatTypeName, datetime.defaultLongTimeFormat, formatTypeValue);
|
||||
return type.is24HourFormat;
|
||||
}
|
||||
|
||||
export function isShortTime24HourFormat(translateFn, formatTypeValue) {
|
||||
const defaultShortTimeFormatTypeName = translateFn('default.shortTimeFormat');
|
||||
const type = utilities.getDateTimeFormatType(datetime.allShortTimeFormat, datetime.allShortTimeFormatArray, defaultShortTimeFormatTypeName, datetime.defaultShortTimeFormat, formatTypeValue);
|
||||
return type.is24HourFormat;
|
||||
}
|
||||
|
||||
export function getAllTimezones(includeSystemDefault, translateFn) {
|
||||
const defaultTimezoneOffset = utilities.getTimezoneOffset();
|
||||
const defaultTimezoneOffsetMinutes = utilities.getTimezoneOffsetMinutes();
|
||||
const allTimezones = timezone.all;
|
||||
const allTimezoneInfos = [];
|
||||
|
||||
for (let i = 0; i < allTimezones.length; i++) {
|
||||
allTimezoneInfos.push({
|
||||
name: allTimezones[i].timezoneName,
|
||||
utcOffset: (allTimezones[i].timezoneName !== timezone.utcTimezoneName ? utilities.getTimezoneOffset(allTimezones[i].timezoneName) : ''),
|
||||
utcOffsetMinutes: utilities.getTimezoneOffsetMinutes(allTimezones[i].timezoneName),
|
||||
displayName: translateFn(`timezone.${allTimezones[i].displayName}`)
|
||||
});
|
||||
}
|
||||
|
||||
if (includeSystemDefault) {
|
||||
allTimezoneInfos.push({
|
||||
name: '',
|
||||
utcOffset: defaultTimezoneOffset,
|
||||
utcOffsetMinutes: defaultTimezoneOffsetMinutes,
|
||||
displayName: translateFn('System Default')
|
||||
});
|
||||
}
|
||||
|
||||
allTimezoneInfos.sort(function(c1, c2) {
|
||||
const utcOffset1 = parseInt(c1.utcOffset.replace(':', ''));
|
||||
const utcOffset2 = parseInt(c2.utcOffset.replace(':', ''));
|
||||
|
||||
if (utcOffset1 !== utcOffset2) {
|
||||
return utcOffset1 - utcOffset2;
|
||||
}
|
||||
|
||||
return c1.displayName.localeCompare(c2.displayName);
|
||||
})
|
||||
|
||||
return allTimezoneInfos;
|
||||
}
|
||||
|
||||
export function getAllCurrencies(translateFn) {
|
||||
const allCurrencyCodes = currency.all;
|
||||
const allCurrencies = [];
|
||||
|
||||
for (let currencyCode in allCurrencyCodes) {
|
||||
if (!Object.prototype.hasOwnProperty.call(allCurrencyCodes, currencyCode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
allCurrencies.push({
|
||||
code: currencyCode,
|
||||
displayName: translateFn(`currency.${currencyCode}`)
|
||||
});
|
||||
}
|
||||
|
||||
allCurrencies.sort(function(c1, c2) {
|
||||
return c1.displayName.localeCompare(c2.displayName);
|
||||
})
|
||||
|
||||
return allCurrencies;
|
||||
}
|
||||
|
||||
export function getDisplayCurrency(value, currencyCode, notConvertValue, translateFn) {
|
||||
if (!utilities.isNumber(value) && !utilities.isString(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (utilities.isNumber(value)) {
|
||||
value = value.toString();
|
||||
}
|
||||
|
||||
if (!notConvertValue) {
|
||||
const hasIncompleteFlag = utilities.isString(value) && value.charAt(value.length - 1) === '+';
|
||||
|
||||
if (hasIncompleteFlag) {
|
||||
value = value.substring(0, value.length - 1);
|
||||
}
|
||||
|
||||
value = utilities.numericCurrencyToString(value);
|
||||
|
||||
if (hasIncompleteFlag) {
|
||||
value = value + '+';
|
||||
}
|
||||
}
|
||||
|
||||
const currencyDisplayMode = settings.getCurrencyDisplayMode();
|
||||
|
||||
if (currencyCode && currencyDisplayMode === currency.allCurrencyDisplayModes.Symbol) {
|
||||
const currencyInfo = currency.all[currencyCode];
|
||||
let currencySymbol = currency.defaultCurrencySymbol;
|
||||
|
||||
if (currencyInfo && currencyInfo.symbol) {
|
||||
currencySymbol = currencyInfo.symbol;
|
||||
} else if (currencyInfo && currencyInfo.code) {
|
||||
currencySymbol = currencyInfo.code;
|
||||
}
|
||||
|
||||
return translateFn('format.currency.symbol', {
|
||||
amount: value,
|
||||
symbol: currencySymbol
|
||||
});
|
||||
} else if (currencyCode && currencyDisplayMode === currency.allCurrencyDisplayModes.Code) {
|
||||
return `${value} ${currencyCode}`;
|
||||
} else if (currencyCode && currencyDisplayMode === currency.allCurrencyDisplayModes.Name) {
|
||||
const currencyName = translateFn(`currency.${currencyCode}`);
|
||||
return `${value} ${currencyName}`;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
export function getI18nOptions() {
|
||||
return {
|
||||
legacy: true,
|
||||
locale: defaultLanguage,
|
||||
fallbackLocale: defaultLanguage,
|
||||
formatFallbackMessages: true,
|
||||
@@ -270,3 +554,34 @@ function getLocaleFromLanguageAlias(alias) {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getDateTimeFormats(translateFn, allFormatMap, allFormatArray, localeFormatPathPrefix, localeDefaultFormatTypeName, systemDefaultFormatType) {
|
||||
const defaultFormat = getDateTimeFormat(translateFn, allFormatMap, allFormatArray,
|
||||
localeFormatPathPrefix, localeDefaultFormatTypeName, systemDefaultFormatType, datetime.defaultDateTimeFormatValue);
|
||||
const ret = [];
|
||||
|
||||
ret.push({
|
||||
type: datetime.defaultDateTimeFormatValue,
|
||||
format: defaultFormat,
|
||||
displayName: `${translateFn('Language Default')} (${utilities.formatTime(utilities.getCurrentDateTime(), defaultFormat)})`
|
||||
});
|
||||
|
||||
for (let i = 0; i < allFormatArray.length; i++) {
|
||||
const formatType = allFormatArray[i];
|
||||
const format = translateFn(`${localeFormatPathPrefix}.${formatType.key}`);
|
||||
|
||||
ret.push({
|
||||
type: formatType.type,
|
||||
format: format,
|
||||
displayName: utilities.formatTime(utilities.getCurrentDateTime(), format)
|
||||
});
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
function getDateTimeFormat(translateFn, allFormatMap, allFormatArray, localeFormatPathPrefix, localeDefaultFormatTypeName, systemDefaultFormatType, formatTypeValue) {
|
||||
const type = utilities.getDateTimeFormatType(allFormatMap, allFormatArray,
|
||||
localeDefaultFormatTypeName, systemDefaultFormatType, formatTypeValue);
|
||||
return translateFn(`${localeFormatPathPrefix}.${type.key}`);
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
export default {
|
||||
getLicense: () => {
|
||||
return process.env.LICENSE;
|
||||
return __EZBOOKKEEPING_LICENSE__; // eslint-disable-line
|
||||
},
|
||||
getThirdPartyLicenses: () => {
|
||||
return process.env.THIRD_PARTY_LICENSES || [];
|
||||
return __EZBOOKKEEPING_THIRD_PARTY_LICENSES__ || []; // eslint-disable-line
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import { f7, f7ready } from 'framework7-vue';
|
||||
|
||||
import settings from "../settings.js";
|
||||
import {
|
||||
getLocalizedError,
|
||||
getLocalizedErrorParameters
|
||||
} from "../i18n.js";
|
||||
|
||||
export function showAlert(message, confirmCallback, translateFn) {
|
||||
let parameters = {};
|
||||
|
||||
if (message && message.error) {
|
||||
const localizedError = getLocalizedError(message.error);
|
||||
message = localizedError.message;
|
||||
parameters = getLocalizedErrorParameters(localizedError.parameters, s => translateFn(s));
|
||||
}
|
||||
|
||||
f7ready((f7) => {
|
||||
f7.dialog.create({
|
||||
title: translateFn('global.app.title'),
|
||||
text: translateFn(message, parameters),
|
||||
animate: settings.isEnableAnimate(),
|
||||
buttons: [
|
||||
{
|
||||
text: translateFn('OK'),
|
||||
onClick: confirmCallback
|
||||
}
|
||||
]
|
||||
}).open();
|
||||
});
|
||||
}
|
||||
|
||||
export function showConfirm(message, confirmCallback, cancelCallback, translateFn) {
|
||||
f7ready((f7) => {
|
||||
f7.dialog.create({
|
||||
title: translateFn('global.app.title'),
|
||||
text: translateFn(message),
|
||||
animate: settings.isEnableAnimate(),
|
||||
buttons: [
|
||||
{
|
||||
text: translateFn('Cancel'),
|
||||
onClick: cancelCallback
|
||||
},
|
||||
{
|
||||
text: translateFn('OK'),
|
||||
onClick: confirmCallback
|
||||
}
|
||||
]
|
||||
}).open();
|
||||
});
|
||||
}
|
||||
|
||||
export function showToast(message, timeout, translateFn) {
|
||||
let parameters = {};
|
||||
|
||||
if (message && message.error) {
|
||||
const localizedError = getLocalizedError(message.error);
|
||||
message = localizedError.message;
|
||||
parameters = getLocalizedErrorParameters(localizedError.parameters, s => translateFn(s));
|
||||
}
|
||||
|
||||
f7ready((f7) => {
|
||||
f7.toast.create({
|
||||
text: translateFn(message, parameters),
|
||||
position: 'center',
|
||||
closeTimeout: timeout || 1500
|
||||
}).open();
|
||||
});
|
||||
}
|
||||
|
||||
export function showLoading(delayConditionFunc, delayMills) {
|
||||
if (!delayConditionFunc) {
|
||||
f7ready((f7) => {
|
||||
return f7.preloader.show();
|
||||
});
|
||||
}
|
||||
|
||||
f7ready((f7) => {
|
||||
setTimeout(() => {
|
||||
if (delayConditionFunc()) {
|
||||
f7.preloader.show();
|
||||
}
|
||||
}, delayMills || 200);
|
||||
});
|
||||
}
|
||||
|
||||
export function hideLoading() {
|
||||
f7ready((f7) => {
|
||||
return f7.preloader.hide();
|
||||
});
|
||||
}
|
||||
|
||||
export function routeBackOnError(f7router, errorPropertyName) {
|
||||
const self = this;
|
||||
const router = f7router;
|
||||
|
||||
const unwatch = self.$watch(errorPropertyName, () => {
|
||||
if (self[errorPropertyName]) {
|
||||
setTimeout(() => {
|
||||
if (unwatch) {
|
||||
unwatch();
|
||||
}
|
||||
|
||||
router.back();
|
||||
}, 200);
|
||||
}
|
||||
}, {
|
||||
immediate: true
|
||||
});
|
||||
}
|
||||
|
||||
export function elements(selector) {
|
||||
return f7.$(selector);
|
||||
}
|
||||
|
||||
export function isModalShowing() {
|
||||
return f7.$('.modal-in').length;
|
||||
}
|
||||
|
||||
export function onSwipeoutDeleted(domId, callback) {
|
||||
f7.swipeout.delete(f7.$('#' + domId), callback);
|
||||
}
|
||||
|
||||
export function autoChangeTextareaSize(el) {
|
||||
f7.$(el).find('textarea').each(el => {
|
||||
el.scrollTop = 0;
|
||||
el.style.height = '';
|
||||
el.style.height = el.scrollHeight + 'px';
|
||||
});
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user