Compare commits

..

31 Commits

Author SHA1 Message Date
Mouriya
3540becf6f vite-plugin-css-injected-by-jsはメンテナンスされていないため、通常のソースマップ生成に影響を与えるので、vite-plugin-lib-injected-cssを使用してください 2024-07-02 12:15:18 +09:00
Tian Dai
63099eda8b Merge branch 'plugin-infar-vite' of ssh.dev.azure.com:v3/alicorn-dev/KintoneAppBuilder/KintoneAppBuilder into feature-data-mapping 2024-06-04 17:49:32 +09:00
Tian Dai
65d89b0462 Windowsのオペレーティングシステムの権限問題のため、Viteの自動URLオープン機能を無効にしました 2024-06-04 17:48:39 +09:00
Tian Dai
c6ded099fa vite devとvite serverはエイリアスではなく、置き換え可能で、問題を修正しました。 2024-06-04 17:41:09 +09:00
Tian Dai
c072233593 Merge branch 'plugin-infar-vite' into feature-data-mapping
# Conflicts:
#	plugin/kintone-addins/package.json
#	plugin/kintone-addins/vite.config.js
2024-06-04 17:21:43 +09:00
Tian Dai
4e296c1555 npm-run-all2を全オペレーティング・システムと依存性管理のサポートに置き換える。ラン・スクリプトをより組み合わせ可能なものに再編成する。 プロジェクトの dotenv サポートを提供する。 2024-06-04 16:16:36 +09:00
016fcaab29 feat:データマッピングアクション変更 2024-06-03 17:39:39 +09:00
Mouriya
bebc1ec9fa フィックス 2024-06-03 08:10:23 +09:00
Mouriya
f71c3d2123 挿入専用のデータ・マッピング・プラグインを追加 2024-06-03 08:08:06 +09:00
Mouriya
d79ce8d06b データ収集プラグインの追加 2024-06-03 07:08:04 +09:00
Mouriya
fc9c3a5e81 条件演算子をカスタマイズし、kintoneクエリ文字列に変換することができます。 2024-06-03 07:07:20 +09:00
Mouriya
6df72a1ae3 Merge commit '372dbe50f7254ed3142a85dac58e624fce695aa7' into feature-data-mapping 2024-06-03 02:16:50 +09:00
Mouriya
372dbe50f7 HTTPサーバーのホットリロード機能を追加。 2024-06-03 02:08:37 +09:00
Mouriya
68fde6d490 Merge commit 'b25c17ab53a848f5692f113c6c3c1177e310d87f' into feature-data-mapping 2024-06-03 00:41:01 +09:00
Mouriya
c398dee21e 誤ったmodelValueの使用法の修正 2024-05-27 19:52:16 +09:00
Mouriya
f2ab310b6d Merge commit 'a6cf95b76d56841e7a5d545c8f0835e44cf4dafc' into feature-data-mapping 2024-05-27 19:27:44 +09:00
Mouriya
ca0f24465b 外部アプリセレクタコンポーネントがある場合、このコンポーネントはアプリを選択せずにフィールドを選択できる。 2024-05-27 19:04:21 +09:00
Mouriya
3cc4b65460 アプリ選択コンポーネントがある場合、アプリまたはフィールドが選択されるまで、それらを無効にすることができる。 2024-05-27 19:03:08 +09:00
Mouriya
a6cf95b76d 新しいアプリ選択コンポーネント 2024-05-27 18:45:34 +09:00
Mouriya
484ab9fdae ファイル名とコンポーネント名を変更し、コンポーネントとして使用できるようにする。 2024-05-27 18:44:36 +09:00
Mouriya
78bba2502f データ・マッピング・コンポーネントの追加 2024-05-25 07:01:58 +09:00
Mouriya
c78b3cb5c0 AppFieldSelectコンポーネントは、パブリック部分をAppFieldSelectBoxとして抽出する。 2024-05-25 07:01:38 +09:00
Mouriya
c3b560dbc9 条件付きコンポーネントは'source'でappidを受け取ることができる。 2024-05-25 04:15:09 +09:00
53aadfcaaa feat:データ集計処理作成 2024-05-24 09:20:19 +09:00
Mouriya
7fb3d08ccb 細部の問題の修正 2024-05-20 03:38:27 +09:00
Mouriya
cf4209333d データ処理書き込み完了 2024-05-17 23:32:14 +09:00
Mouriya
61ac281134 verNameのラッピング・オブジェクト 2024-05-17 14:41:15 +09:00
Mouriya
b25c17ab53 cssとjsを1つのファイルにまとめるためのviteプラグインを追加。 2024-05-13 16:11:52 +09:00
Mouriya
64d2cadd82 2つのデータ計算コンポーネントを追加する 2024-05-13 06:56:44 +09:00
Mouriya
371e2ee073 add vueuse dependencies 2024-05-13 06:56:03 +09:00
Mouriya
a7078b54c5 イベントに削除関数を追加し、即座に適用する 2024-05-13 01:16:09 +09:00
59 changed files with 3434 additions and 1601 deletions

View File

@@ -1,502 +0,0 @@
<#
.Synopsis
Activate a Python virtual environment for the current PowerShell session.
.Description
Pushes the python executable for a virtual environment to the front of the
$Env:PATH environment variable and sets the prompt to signify that you are
in a Python virtual environment. Makes use of the command line switches as
well as the `pyvenv.cfg` file values present in the virtual environment.
.Parameter VenvDir
Path to the directory that contains the virtual environment to activate. The
default value for this is the parent of the directory that the Activate.ps1
script is located within.
.Parameter Prompt
The prompt prefix to display when this virtual environment is activated. By
default, this prompt is the name of the virtual environment folder (VenvDir)
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
.Example
Activate.ps1
Activates the Python virtual environment that contains the Activate.ps1 script.
.Example
Activate.ps1 -Verbose
Activates the Python virtual environment that contains the Activate.ps1 script,
and shows extra information about the activation as it executes.
.Example
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
Activates the Python virtual environment located in the specified location.
.Example
Activate.ps1 -Prompt "MyPython"
Activates the Python virtual environment that contains the Activate.ps1 script,
and prefixes the current prompt with the specified string (surrounded in
parentheses) while the virtual environment is active.
.Notes
On Windows, it may be required to enable this Activate.ps1 script by setting the
execution policy for the user. You can do this by issuing the following PowerShell
command:
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
For more information on Execution Policies:
https://go.microsoft.com/fwlink/?LinkID=135170
#>
Param(
[Parameter(Mandatory = $false)]
[String]
$VenvDir,
[Parameter(Mandatory = $false)]
[String]
$Prompt
)
<# Function declarations --------------------------------------------------- #>
<#
.Synopsis
Remove all shell session elements added by the Activate script, including the
addition of the virtual environment's Python executable from the beginning of
the PATH variable.
.Parameter NonDestructive
If present, do not remove this function from the global namespace for the
session.
#>
function global:deactivate ([switch]$NonDestructive) {
# Revert to original values
# The prior prompt:
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
}
# The prior PYTHONHOME:
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
}
# The prior PATH:
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
}
# Just remove the VIRTUAL_ENV altogether:
if (Test-Path -Path Env:VIRTUAL_ENV) {
Remove-Item -Path env:VIRTUAL_ENV
}
# Just remove VIRTUAL_ENV_PROMPT altogether.
if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
Remove-Item -Path env:VIRTUAL_ENV_PROMPT
}
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
}
# Leave deactivate function in the global namespace if requested:
if (-not $NonDestructive) {
Remove-Item -Path function:deactivate
}
}
<#
.Description
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
given folder, and returns them in a map.
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
two strings separated by `=` (with any amount of whitespace surrounding the =)
then it is considered a `key = value` line. The left hand string is the key,
the right hand is the value.
If the value starts with a `'` or a `"` then the first and last character is
stripped from the value before being captured.
.Parameter ConfigDir
Path to the directory that contains the `pyvenv.cfg` file.
#>
function Get-PyVenvConfig(
[String]
$ConfigDir
) {
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
# An empty map will be returned if no config file is found.
$pyvenvConfig = @{ }
if ($pyvenvConfigPath) {
Write-Verbose "File exists, parse `key = value` lines"
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
$pyvenvConfigContent | ForEach-Object {
$keyval = $PSItem -split "\s*=\s*", 2
if ($keyval[0] -and $keyval[1]) {
$val = $keyval[1]
# Remove extraneous quotations around a string value.
if ("'""".Contains($val.Substring(0, 1))) {
$val = $val.Substring(1, $val.Length - 2)
}
$pyvenvConfig[$keyval[0]] = $val
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
}
}
}
return $pyvenvConfig
}
<# Begin Activate script --------------------------------------------------- #>
# Determine the containing directory of this script
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
$VenvExecDir = Get-Item -Path $VenvExecPath
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
# Set values required in priority: CmdLine, ConfigFile, Default
# First, get the location of the virtual environment, it might not be
# VenvExecDir if specified on the command line.
if ($VenvDir) {
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
}
else {
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
Write-Verbose "VenvDir=$VenvDir"
}
# Next, read the `pyvenv.cfg` file to determine any required value such
# as `prompt`.
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
# Next, set the prompt from the command line, or the config file, or
# just use the name of the virtual environment folder.
if ($Prompt) {
Write-Verbose "Prompt specified as argument, using '$Prompt'"
}
else {
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
$Prompt = $pyvenvCfg['prompt'];
}
else {
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
$Prompt = Split-Path -Path $venvDir -Leaf
}
}
Write-Verbose "Prompt = '$Prompt'"
Write-Verbose "VenvDir='$VenvDir'"
# Deactivate any currently active virtual environment, but leave the
# deactivate function in place.
deactivate -nondestructive
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
# that there is an activated venv.
$env:VIRTUAL_ENV = $VenvDir
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
Write-Verbose "Setting prompt to '$Prompt'"
# Set the prompt to include the env name
# Make sure _OLD_VIRTUAL_PROMPT is global
function global:_OLD_VIRTUAL_PROMPT { "" }
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
function global:prompt {
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
_OLD_VIRTUAL_PROMPT
}
$env:VIRTUAL_ENV_PROMPT = $Prompt
}
# Clear PYTHONHOME
if (Test-Path -Path Env:PYTHONHOME) {
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
Remove-Item -Path Env:PYTHONHOME
}
# Add the venv to the PATH
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"
# SIG # Begin signature block
# MIIvIgYJKoZIhvcNAQcCoIIvEzCCLw8CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBnL745ElCYk8vk
# dBtMuQhLeWJ3ZGfzKW4DHCYzAn+QB6CCE8MwggWQMIIDeKADAgECAhAFmxtXno4h
# MuI5B72nd3VcMA0GCSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNV
# BAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0z
# ODAxMTUxMjAwMDBaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0
# IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
# AL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/z
# G6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZ
# anMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7s
# Wxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL
# 2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfb
# BHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3
# JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3c
# AORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqx
# YxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0
# viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aL
# T8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1Ud
# EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzf
# Lmc/57qYrhwPTzANBgkqhkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNk
# aA9Wz3eucPn9mkqZucl4XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjS
# PMFDQK4dUPVS/JA7u5iZaWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK
# 7VB6fWIhCoDIc2bRoAVgX+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eB
# cg3AFDLvMFkuruBx8lbkapdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp
# 5aPNoiBB19GcZNnqJqGLFNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msg
# dDDS4Dk0EIUhFQEI6FUy3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vri
# RbgjU2wGb2dVf0a1TD9uKFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ7
# 9ARj6e/CVABRoIoqyc54zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5
# nLGbsQAe79APT0JsyQq87kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3
# i0objwG2J5VT6LaJbVu8aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0H
# EEcRrYc9B9F1vM/zZn4wggawMIIEmKADAgECAhAIrUCyYNKcTJ9ezam9k67ZMA0G
# CSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0
# IFRydXN0ZWQgUm9vdCBHNDAeFw0yMTA0MjkwMDAwMDBaFw0zNjA0MjgyMzU5NTla
# MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE
# AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz
# ODQgMjAyMSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDVtC9C
# 0CiteLdd1TlZG7GIQvUzjOs9gZdwxbvEhSYwn6SOaNhc9es0JAfhS0/TeEP0F9ce
# 2vnS1WcaUk8OoVf8iJnBkcyBAz5NcCRks43iCH00fUyAVxJrQ5qZ8sU7H/Lvy0da
# E6ZMswEgJfMQ04uy+wjwiuCdCcBlp/qYgEk1hz1RGeiQIXhFLqGfLOEYwhrMxe6T
# SXBCMo/7xuoc82VokaJNTIIRSFJo3hC9FFdd6BgTZcV/sk+FLEikVoQ11vkunKoA
# FdE3/hoGlMJ8yOobMubKwvSnowMOdKWvObarYBLj6Na59zHh3K3kGKDYwSNHR7Oh
# D26jq22YBoMbt2pnLdK9RBqSEIGPsDsJ18ebMlrC/2pgVItJwZPt4bRc4G/rJvmM
# 1bL5OBDm6s6R9b7T+2+TYTRcvJNFKIM2KmYoX7BzzosmJQayg9Rc9hUZTO1i4F4z
# 8ujo7AqnsAMrkbI2eb73rQgedaZlzLvjSFDzd5Ea/ttQokbIYViY9XwCFjyDKK05
# huzUtw1T0PhH5nUwjewwk3YUpltLXXRhTT8SkXbev1jLchApQfDVxW0mdmgRQRNY
# mtwmKwH0iU1Z23jPgUo+QEdfyYFQc4UQIyFZYIpkVMHMIRroOBl8ZhzNeDhFMJlP
# /2NPTLuqDQhTQXxYPUez+rbsjDIJAsxsPAxWEQIDAQABo4IBWTCCAVUwEgYDVR0T
# AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHwYD
# VR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMG
# A1UdJQQMMAoGCCsGAQUFBwMDMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYY
# aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2Fj
# ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNV
# HR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRU
# cnVzdGVkUm9vdEc0LmNybDAcBgNVHSAEFTATMAcGBWeBDAEDMAgGBmeBDAEEATAN
# BgkqhkiG9w0BAQwFAAOCAgEAOiNEPY0Idu6PvDqZ01bgAhql+Eg08yy25nRm95Ry
# sQDKr2wwJxMSnpBEn0v9nqN8JtU3vDpdSG2V1T9J9Ce7FoFFUP2cvbaF4HZ+N3HL
# IvdaqpDP9ZNq4+sg0dVQeYiaiorBtr2hSBh+3NiAGhEZGM1hmYFW9snjdufE5Btf
# Q/g+lP92OT2e1JnPSt0o618moZVYSNUa/tcnP/2Q0XaG3RywYFzzDaju4ImhvTnh
# OE7abrs2nfvlIVNaw8rpavGiPttDuDPITzgUkpn13c5UbdldAhQfQDN8A+KVssIh
# dXNSy0bYxDQcoqVLjc1vdjcshT8azibpGL6QB7BDf5WIIIJw8MzK7/0pNVwfiThV
# 9zeKiwmhywvpMRr/LhlcOXHhvpynCgbWJme3kuZOX956rEnPLqR0kq3bPKSchh/j
# wVYbKyP/j7XqiHtwa+aguv06P0WmxOgWkVKLQcBIhEuWTatEQOON8BUozu3xGFYH
# Ki8QxAwIZDwzj64ojDzLj4gLDb879M4ee47vtevLt/B3E+bnKD+sEq6lLyJsQfmC
# XBVmzGwOysWGw/YmMwwHS6DTBwJqakAwSEs0qFEgu60bhQjiWQ1tygVQK+pKHJ6l
# /aCnHwZ05/LWUpD9r4VIIflXO7ScA+2GRfS0YW6/aOImYIbqyK+p/pQd52MbOoZW
# eE4wggd3MIIFX6ADAgECAhAHHxQbizANJfMU6yMM0NHdMA0GCSqGSIb3DQEBCwUA
# MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE
# AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz
# ODQgMjAyMSBDQTEwHhcNMjIwMTE3MDAwMDAwWhcNMjUwMTE1MjM1OTU5WjB8MQsw
# CQYDVQQGEwJVUzEPMA0GA1UECBMGT3JlZ29uMRIwEAYDVQQHEwlCZWF2ZXJ0b24x
# IzAhBgNVBAoTGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMSMwIQYDVQQDExpQ
# eXRob24gU29mdHdhcmUgRm91bmRhdGlvbjCCAiIwDQYJKoZIhvcNAQEBBQADggIP
# ADCCAgoCggIBAKgc0BTT+iKbtK6f2mr9pNMUTcAJxKdsuOiSYgDFfwhjQy89koM7
# uP+QV/gwx8MzEt3c9tLJvDccVWQ8H7mVsk/K+X+IufBLCgUi0GGAZUegEAeRlSXx
# xhYScr818ma8EvGIZdiSOhqjYc4KnfgfIS4RLtZSrDFG2tN16yS8skFa3IHyvWdb
# D9PvZ4iYNAS4pjYDRjT/9uzPZ4Pan+53xZIcDgjiTwOh8VGuppxcia6a7xCyKoOA
# GjvCyQsj5223v1/Ig7Dp9mGI+nh1E3IwmyTIIuVHyK6Lqu352diDY+iCMpk9Zanm
# SjmB+GMVs+H/gOiofjjtf6oz0ki3rb7sQ8fTnonIL9dyGTJ0ZFYKeb6BLA66d2GA
# LwxZhLe5WH4Np9HcyXHACkppsE6ynYjTOd7+jN1PRJahN1oERzTzEiV6nCO1M3U1
# HbPTGyq52IMFSBM2/07WTJSbOeXjvYR7aUxK9/ZkJiacl2iZI7IWe7JKhHohqKuc
# eQNyOzxTakLcRkzynvIrk33R9YVqtB4L6wtFxhUjvDnQg16xot2KVPdfyPAWd81w
# tZADmrUtsZ9qG79x1hBdyOl4vUtVPECuyhCxaw+faVjumapPUnwo8ygflJJ74J+B
# Yxf6UuD7m8yzsfXWkdv52DjL74TxzuFTLHPyARWCSCAbzn3ZIly+qIqDAgMBAAGj
# ggIGMIICAjAfBgNVHSMEGDAWgBRoN+Drtjv4XxGG+/5hewiIZfROQjAdBgNVHQ4E
# FgQUt/1Teh2XDuUj2WW3siYWJgkZHA8wDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQM
# MAoGCCsGAQUFBwMDMIG1BgNVHR8Ega0wgaowU6BRoE+GTWh0dHA6Ly9jcmwzLmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNI
# QTM4NDIwMjFDQTEuY3JsMFOgUaBPhk1odHRwOi8vY3JsNC5kaWdpY2VydC5jb20v
# RGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0Ex
# LmNybDA+BgNVHSAENzA1MDMGBmeBDAEEATApMCcGCCsGAQUFBwIBFhtodHRwOi8v
# d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwgZQGCCsGAQUFBwEBBIGHMIGEMCQGCCsGAQUF
# BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wXAYIKwYBBQUHMAKGUGh0dHA6
# Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWdu
# aW5nUlNBNDA5NlNIQTM4NDIwMjFDQTEuY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZI
# hvcNAQELBQADggIBABxv4AeV/5ltkELHSC63fXAFYS5tadcWTiNc2rskrNLrfH1N
# s0vgSZFoQxYBFKI159E8oQQ1SKbTEubZ/B9kmHPhprHya08+VVzxC88pOEvz68nA
# 82oEM09584aILqYmj8Pj7h/kmZNzuEL7WiwFa/U1hX+XiWfLIJQsAHBla0i7QRF2
# de8/VSF0XXFa2kBQ6aiTsiLyKPNbaNtbcucaUdn6vVUS5izWOXM95BSkFSKdE45O
# q3FForNJXjBvSCpwcP36WklaHL+aHu1upIhCTUkzTHMh8b86WmjRUqbrnvdyR2yd
# I5l1OqcMBjkpPpIV6wcc+KY/RH2xvVuuoHjlUjwq2bHiNoX+W1scCpnA8YTs2d50
# jDHUgwUo+ciwpffH0Riq132NFmrH3r67VaN3TuBxjI8SIZM58WEDkbeoriDk3hxU
# 8ZWV7b8AW6oyVBGfM06UgkfMb58h+tJPrFx8VI/WLq1dTqMfZOm5cuclMnUHs2uq
# rRNtnV8UfidPBL4ZHkTcClQbCoz0UbLhkiDvIS00Dn+BBcxw/TKqVL4Oaz3bkMSs
# M46LciTeucHY9ExRVt3zy7i149sd+F4QozPqn7FrSVHXmem3r7bjyHTxOgqxRCVa
# 18Vtx7P/8bYSBeS+WHCKcliFCecspusCDSlnRUjZwyPdP0VHxaZg2unjHY3rMYIa
# tTCCGrECAQEwfTBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIElu
# Yy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBTaWduaW5nIFJT
# QTQwOTYgU0hBMzg0IDIwMjEgQ0ExAhAHHxQbizANJfMU6yMM0NHdMA0GCWCGSAFl
# AwQCAQUAoIHIMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcC
# AQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCBnAZ6P7YvTwq0fbF62
# o7E75R0LxsW5OtyYiFESQckLhjBcBgorBgEEAYI3AgEMMU4wTKBGgEQAQgB1AGkA
# bAB0ADoAIABSAGUAbABlAGEAcwBlAF8AdgAzAC4AMQAxAC4ANABfADIAMAAyADMA
# MAA2ADAANwAuADAAMaECgAAwDQYJKoZIhvcNAQEBBQAEggIAVdxtEr9NH8SoVTzT
# o/jdr3t1yqExSecge3YGCu9USfMqLtmCKzG5r2rf3xZkJ6CpvmHwji3FUY6Hl991
# Ttd0eEEpjeEse9gotnojgHTQACJntGuPcK+65jIQYNvp3JIuczjTW0JjWkJf4lqI
# hVS6rEc00D/0NsUF9BbNkjNZ0AUQeOWe2WZJnqRRFN4U3pToN51NDjpEtRjlNTkc
# SzoNO7ZyEsSXkNenlgbgS1yXEQ8v4bbnbPyyL+2yWMG1QsLv6M3OV0kXx9aow1r5
# gZ1mCjBkbtWKH58WVBoepUaPYTjFBWCT2pDrorbg6cguwBdyz7s8X+WlCD4ycFfW
# o95x7u1W9RwPPPppszr8Pd4jZSbEXEQ/G9Ke5NvTvNmK93b7/kySfNYfwW2meP6E
# JIc0R9DMSZlK+ChtU5mmvo4e6YQTLXIXQhPIz7jVNlUjXMJX7WALjE72EDdC5MpQ
# ygW7wue6EhjlUVXT4pEIySCGaXxUzRi1oh+Q+Jbe3rDvhSPZUWzCqEtOkJ35dLYh
# D9Rahi2BM1qaepfu1wVtSXbVbc0SDPjloojEmTyDnk61u5epo0E0oHqNAU8t1ZTN
# +Guptl/agMp52uRsaC5Bi276icqRtclfx9E4SfJEiw7xRlImCclMpw2dRsyzIrpb
# MKpWDAno4rClgYS3M9lqQ71RlXehghc+MIIXOgYKKwYBBAGCNwMDATGCFyowghcm
# BgkqhkiG9w0BBwKgghcXMIIXEwIBAzEPMA0GCWCGSAFlAwQCAQUAMHgGCyqGSIb3
# DQEJEAEEoGkEZzBlAgEBBglghkgBhv1sBwEwMTANBglghkgBZQMEAgEFAAQgsPGH
# UIiYgGXi/94WNZrP+V1kV/B5SVJn3ck+XzTJ0aACEQCJ79BpOkCDCW06IgZOU3EQ
# GA8yMDIzMDYwNzA1NTc0NFqgghMHMIIGwDCCBKigAwIBAgIQDE1pckuU+jwqSj0p
# B4A9WjANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln
# aUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5
# NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTIyMDkyMTAwMDAwMFoXDTMzMTEy
# MTIzNTk1OVowRjELMAkGA1UEBhMCVVMxETAPBgNVBAoTCERpZ2lDZXJ0MSQwIgYD
# VQQDExtEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMiAtIDIwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQDP7KUmOsap8mu7jcENmtuh6BSFdDMaJqzQHFUeHjZt
# vJJVDGH0nQl3PRWWCC9rZKT9BoMW15GSOBwxApb7crGXOlWvM+xhiummKNuQY1y9
# iVPgOi2Mh0KuJqTku3h4uXoW4VbGwLpkU7sqFudQSLuIaQyIxvG+4C99O7HKU41A
# gx7ny3JJKB5MgB6FVueF7fJhvKo6B332q27lZt3iXPUv7Y3UTZWEaOOAy2p50dIQ
# kUYp6z4m8rSMzUy5Zsi7qlA4DeWMlF0ZWr/1e0BubxaompyVR4aFeT4MXmaMGgok
# vpyq0py2909ueMQoP6McD1AGN7oI2TWmtR7aeFgdOej4TJEQln5N4d3CraV++C0b
# H+wrRhijGfY59/XBT3EuiQMRoku7mL/6T+R7Nu8GRORV/zbq5Xwx5/PCUsTmFnta
# fqUlc9vAapkhLWPlWfVNL5AfJ7fSqxTlOGaHUQhr+1NDOdBk+lbP4PQK5hRtZHi7
# mP2Uw3Mh8y/CLiDXgazT8QfU4b3ZXUtuMZQpi+ZBpGWUwFjl5S4pkKa3YWT62SBs
# GFFguqaBDwklU/G/O+mrBw5qBzliGcnWhX8T2Y15z2LF7OF7ucxnEweawXjtxojI
# sG4yeccLWYONxu71LHx7jstkifGxxLjnU15fVdJ9GSlZA076XepFcxyEftfO4tQ6
# dwIDAQABo4IBizCCAYcwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwFgYD
# VR0lAQH/BAwwCgYIKwYBBQUHAwgwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZI
# AYb9bAcBMB8GA1UdIwQYMBaAFLoW2W1NhS9zKXaaL3WMaiCPnshvMB0GA1UdDgQW
# BBRiit7QYfyPMRTtlwvNPSqUFN9SnDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8v
# Y3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRSU0E0MDk2U0hBMjU2
# VGltZVN0YW1waW5nQ0EuY3JsMIGQBggrBgEFBQcBAQSBgzCBgDAkBggrBgEFBQcw
# AYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFgGCCsGAQUFBzAChkxodHRwOi8v
# Y2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRSU0E0MDk2U0hB
# MjU2VGltZVN0YW1waW5nQ0EuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQBVqioa80bz
# eFc3MPx140/WhSPx/PmVOZsl5vdyipjDd9Rk/BX7NsJJUSx4iGNVCUY5APxp1Mqb
# KfujP8DJAJsTHbCYidx48s18hc1Tna9i4mFmoxQqRYdKmEIrUPwbtZ4IMAn65C3X
# CYl5+QnmiM59G7hqopvBU2AJ6KO4ndetHxy47JhB8PYOgPvk/9+dEKfrALpfSo8a
# OlK06r8JSRU1NlmaD1TSsht/fl4JrXZUinRtytIFZyt26/+YsiaVOBmIRBTlClmi
# a+ciPkQh0j8cwJvtfEiy2JIMkU88ZpSvXQJT657inuTTH4YBZJwAwuladHUNPeF5
# iL8cAZfJGSOA1zZaX5YWsWMMxkZAO85dNdRZPkOaGK7DycvD+5sTX2q1x+DzBcNZ
# 3ydiK95ByVO5/zQQZ/YmMph7/lxClIGUgp2sCovGSxVK05iQRWAzgOAj3vgDpPZF
# R+XOuANCR+hBNnF3rf2i6Jd0Ti7aHh2MWsgemtXC8MYiqE+bvdgcmlHEL5r2X6cn
# l7qWLoVXwGDneFZ/au/ClZpLEQLIgpzJGgV8unG1TnqZbPTontRamMifv427GFxD
# 9dAq6OJi7ngE273R+1sKqHB+8JeEeOMIA11HLGOoJTiXAdI/Otrl5fbmm9x+LMz/
# F0xNAKLY1gEOuIvu5uByVYksJxlh9ncBjDCCBq4wggSWoAMCAQICEAc2N7ckVHzY
# R6z9KGYqXlswDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCVVMxFTATBgNVBAoT
# DERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UE
# AxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MB4XDTIyMDMyMzAwMDAwMFoXDTM3
# MDMyMjIzNTk1OVowYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ
# bmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2
# IFRpbWVTdGFtcGluZyBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
# AMaGNQZJs8E9cklRVcclA8TykTepl1Gh1tKD0Z5Mom2gsMyD+Vr2EaFEFUJfpIjz
# aPp985yJC3+dH54PMx9QEwsmc5Zt+FeoAn39Q7SE2hHxc7Gz7iuAhIoiGN/r2j3E
# F3+rGSs+QtxnjupRPfDWVtTnKC3r07G1decfBmWNlCnT2exp39mQh0YAe9tEQYnc
# fGpXevA3eZ9drMvohGS0UvJ2R/dhgxndX7RUCyFobjchu0CsX7LeSn3O9TkSZ+8O
# pWNs5KbFHc02DVzV5huowWR0QKfAcsW6Th+xtVhNef7Xj3OTrCw54qVI1vCwMROp
# VymWJy71h6aPTnYVVSZwmCZ/oBpHIEPjQ2OAe3VuJyWQmDo4EbP29p7mO1vsgd4i
# FNmCKseSv6De4z6ic/rnH1pslPJSlRErWHRAKKtzQ87fSqEcazjFKfPKqpZzQmif
# tkaznTqj1QPgv/CiPMpC3BhIfxQ0z9JMq++bPf4OuGQq+nUoJEHtQr8FnGZJUlD0
# UfM2SU2LINIsVzV5K6jzRWC8I41Y99xh3pP+OcD5sjClTNfpmEpYPtMDiP6zj9Ne
# S3YSUZPJjAw7W4oiqMEmCPkUEBIDfV8ju2TjY+Cm4T72wnSyPx4JduyrXUZ14mCj
# WAkBKAAOhFTuzuldyF4wEr1GnrXTdrnSDmuZDNIztM2xAgMBAAGjggFdMIIBWTAS
# BgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBS6FtltTYUvcyl2mi91jGogj57I
# bzAfBgNVHSMEGDAWgBTs1+OC0nFdZEzfLmc/57qYrhwPTzAOBgNVHQ8BAf8EBAMC
# AYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwdwYIKwYBBQUHAQEEazBpMCQGCCsGAQUF
# BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQQYIKwYBBQUHMAKGNWh0dHA6
# Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290RzQuY3J0
# MEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdp
# Q2VydFRydXN0ZWRSb290RzQuY3JsMCAGA1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCG
# SAGG/WwHATANBgkqhkiG9w0BAQsFAAOCAgEAfVmOwJO2b5ipRCIBfmbW2CFC4bAY
# LhBNE88wU86/GPvHUF3iSyn7cIoNqilp/GnBzx0H6T5gyNgL5Vxb122H+oQgJTQx
# Z822EpZvxFBMYh0MCIKoFr2pVs8Vc40BIiXOlWk/R3f7cnQU1/+rT4osequFzUNf
# 7WC2qk+RZp4snuCKrOX9jLxkJodskr2dfNBwCnzvqLx1T7pa96kQsl3p/yhUifDV
# inF2ZdrM8HKjI/rAJ4JErpknG6skHibBt94q6/aesXmZgaNWhqsKRcnfxI2g55j7
# +6adcq/Ex8HBanHZxhOACcS2n82HhyS7T6NJuXdmkfFynOlLAlKnN36TU6w7HQhJ
# D5TNOXrd/yVjmScsPT9rp/Fmw0HNT7ZAmyEhQNC3EyTN3B14OuSereU0cZLXJmvk
# OHOrpgFPvT87eK1MrfvElXvtCl8zOYdBeHo46Zzh3SP9HSjTx/no8Zhf+yvYfvJG
# nXUsHicsJttvFXseGYs2uJPU5vIXmVnKcPA3v5gA3yAWTyf7YGcWoWa63VXAOimG
# sJigK+2VQbc61RWYMbRiCQ8KvYHZE/6/pNHzV9m8BPqC3jLfBInwAM1dwvnQI38A
# C+R2AibZ8GV2QqYphwlHK+Z/GqSFD/yYlvZVVCsfgPrA8g4r5db7qS9EFUrnEw4d
# 2zc4GqEr9u3WfPwwggWNMIIEdaADAgECAhAOmxiO+dAt5+/bUOIIQBhaMA0GCSqG
# SIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx
# GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFz
# c3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBaFw0zMTExMDkyMzU5NTla
# MGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsT
# EHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9v
# dCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvkXUo8
# MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdtHauy
# efLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu34Lz
# B4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0QF+x
# embud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2kZhA
# kHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM1Lyu
# GwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmIdph2
# PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZK37A
# lLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72gLD7
# 6GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqsX40/
# ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyhHsXA
# j6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIBNjAPBgNVHRMBAf8EBTAD
# AQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwPTzAfBgNVHSMEGDAWgBRF
# 66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMCAYYweQYIKwYBBQUHAQEE
# bTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYB
# BQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3Vy
# ZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0aHR0cDovL2NybDMuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDARBgNVHSAECjAI
# MAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0NcVec4X6CjdBs9thbX979X
# B72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnovLbc47/T/gLn4offyct4k
# vFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65ZyoUi0mcudT6cGAxN3J0TU
# 53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFWjuyk1T3osdz9HNj0d1pc
# VIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPFmCLBsln1VWvPJ6tsds5v
# Iy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9ztwGpn1eqXijiuZQxggN2
# MIIDcgIBATB3MGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5j
# LjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBU
# aW1lU3RhbXBpbmcgQ0ECEAxNaXJLlPo8Kko9KQeAPVowDQYJYIZIAWUDBAIBBQCg
# gdEwGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0y
# MzA2MDcwNTU3NDRaMCsGCyqGSIb3DQEJEAIMMRwwGjAYMBYEFPOHIk2GM4KSNamU
# vL2Plun+HHxzMC8GCSqGSIb3DQEJBDEiBCAZCWWaBjTHAZnsndSxyxCaZSOrTyqo
# O35hv3VOlS9KHDA3BgsqhkiG9w0BCRACLzEoMCYwJDAiBCDH9OG+MiiJIKviJjq+
# GsT8T+Z4HC1k0EyAdVegI7W2+jANBgkqhkiG9w0BAQEFAASCAgBt92vHxbHXh4Z2
# yl+aTo7PltgPhZhoQCWg+gDSyySEqkDN+kTuoW3ROuMjR1JR0htJOwVqnmI/enhW
# r8VJiDKfGOGupHEfzAlMaIIC+K+C3sSoeaRR1aiOWrUA/oPpJIgwXyfo65Hf07qf
# wdn//4y5zv6oMdHNtpSfFgibze5BjNRAgOUxl9rvKArEN7B+WTCnvLWw/EJe48MQ
# B0zUbVFIORQUHlLnCL07JGRSN5bHaMtnn5eEwZFC9522kJaHyLrmfeP4jZLMhjhn
# fGxv69HVzggM8CpjpQA8l8hh6Il48TDMZpdqkxwjoRmJVwt3hwTrfuE11NFrXEAD
# 8dAAta6N/M722c3BE6UxM2R4QXyV05BL6e4jVJm1aR1ebUVS4nZVZ/jbCexR/+vx
# mfSh1SezU3KlgRMDrLF+El883BFoe/99p4/QjjnELhn41lPPAYGefhMI9ioYZULQ
# xMyG6qIPA8s2tnYIL/AKvh7SUgFVtOsTKbTFXMMr20sipBQQFUOb8ZD+8u4Iyc4M
# UC4d2S6z9zwlPbSr1lk9m3R8rl+j2/VkB1S21nqda3xWFk/+n/2oEJe4gUkCiQxs
# qFaykkcAhWYdZVRRNM89ZF23DeYAEkUEaD2M1ld0CZNtvtPNmv/NZV/Xbb3H0RPR
# yDBB2JQI1BbEjl7HWy616MUsAqWA+Q==
# SIG # End signature block

View File

@@ -1,69 +0,0 @@
# This file must be used with "source bin/activate" *from bash*
# you cannot run it directly
deactivate () {
# reset old environment variables
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
PATH="${_OLD_VIRTUAL_PATH:-}"
export PATH
unset _OLD_VIRTUAL_PATH
fi
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
export PYTHONHOME
unset _OLD_VIRTUAL_PYTHONHOME
fi
# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
hash -r 2> /dev/null
fi
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
PS1="${_OLD_VIRTUAL_PS1:-}"
export PS1
unset _OLD_VIRTUAL_PS1
fi
unset VIRTUAL_ENV
unset VIRTUAL_ENV_PROMPT
if [ ! "${1:-}" = "nondestructive" ] ; then
# Self destruct!
unset -f deactivate
fi
}
# unset irrelevant variables
deactivate nondestructive
VIRTUAL_ENV="C:\project\Kintone\KintoneAppBuilder\backend"
export VIRTUAL_ENV
_OLD_VIRTUAL_PATH="$PATH"
PATH="$VIRTUAL_ENV/Scripts:$PATH"
export PATH
# unset PYTHONHOME if set
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
# could use `if (set -u; : $PYTHONHOME) ;` in bash
if [ -n "${PYTHONHOME:-}" ] ; then
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
unset PYTHONHOME
fi
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
_OLD_VIRTUAL_PS1="${PS1:-}"
PS1="(backend) ${PS1:-}"
export PS1
VIRTUAL_ENV_PROMPT="(backend) "
export VIRTUAL_ENV_PROMPT
fi
# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
hash -r 2> /dev/null
fi

View File

@@ -1,34 +0,0 @@
@echo off
rem This file is UTF-8 encoded, so we need to update the current code page while executing it
for /f "tokens=2 delims=:." %%a in ('"%SystemRoot%\System32\chcp.com"') do (
set _OLD_CODEPAGE=%%a
)
if defined _OLD_CODEPAGE (
"%SystemRoot%\System32\chcp.com" 65001 > nul
)
set VIRTUAL_ENV=C:\project\Kintone\KintoneAppBuilder\backend
if not defined PROMPT set PROMPT=$P$G
if defined _OLD_VIRTUAL_PROMPT set PROMPT=%_OLD_VIRTUAL_PROMPT%
if defined _OLD_VIRTUAL_PYTHONHOME set PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%
set _OLD_VIRTUAL_PROMPT=%PROMPT%
set PROMPT=(backend) %PROMPT%
if defined PYTHONHOME set _OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME%
set PYTHONHOME=
if defined _OLD_VIRTUAL_PATH set PATH=%_OLD_VIRTUAL_PATH%
if not defined _OLD_VIRTUAL_PATH set _OLD_VIRTUAL_PATH=%PATH%
set PATH=%VIRTUAL_ENV%\Scripts;%PATH%
set VIRTUAL_ENV_PROMPT=(backend)
:END
if defined _OLD_CODEPAGE (
"%SystemRoot%\System32\chcp.com" %_OLD_CODEPAGE% > nul
set _OLD_CODEPAGE=
)

View File

@@ -1,22 +0,0 @@
@echo off
if defined _OLD_VIRTUAL_PROMPT (
set "PROMPT=%_OLD_VIRTUAL_PROMPT%"
)
set _OLD_VIRTUAL_PROMPT=
if defined _OLD_VIRTUAL_PYTHONHOME (
set "PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%"
set _OLD_VIRTUAL_PYTHONHOME=
)
if defined _OLD_VIRTUAL_PATH (
set "PATH=%_OLD_VIRTUAL_PATH%"
)
set _OLD_VIRTUAL_PATH=
set VIRTUAL_ENV=
set VIRTUAL_ENV_PROMPT=
:END

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -13,7 +13,7 @@ API_V1_AUTH_KEY = "X-Cybozu-Authorization"
DEPLOY_MODE = "DEV" #DEV,PROD
DEPLOY_JS_URL = "https://ka-addin.azurewebsites.net/alc_runtime.js"
#DEPLOY_JS_URL = "https://92dc-133-139-109-57.ngrok-free.app/alc_runtime.js"
#DEPLOY_JS_URL = "https://e84c-133-139-70-142.ngrok-free.app/alc_runtime.js"
KINTONE_FIELD_TYPE=["GROUP","GROUP_SELECT","CHECK_BOX","SUBTABLE","DROP_DOWN","USER_SELECT","RADIO_BUTTON","RICH_TEXT","LINK","REFERENCE_TABLE","CALC","TIME","NUMBER","ORGANIZATION_SELECT","FILE","DATETIME","DATE","MULTI_SELECT","SINGLE_LINE_TEXT","MULTI_LINE_TEXT"]

View File

@@ -1,2 +1,3 @@
KAB_BACKEND_URL="https://kab-backend.azurewebsites.net/"
#KAB_BACKEND_URL="http://127.0.0.1:8000/"

View File

@@ -2,7 +2,6 @@
<html lang="ja-jp">
<head>
<title><%= productName %></title>
<meta charset="utf-8">
<meta name="description" content="<%= productDescription %>">
<meta name="format-detection" content="telephone=no">

View File

@@ -17,8 +17,9 @@
},
"dependencies": {
"@quasar/extras": "^1.16.4",
"@vueuse/core": "^10.9.0",
"axios": "^1.4.0",
"pinia": "^2.1.6",
"pinia": "^2.1.7",
"quasar": "^2.6.0",
"uuid": "^9.0.0",
"vue": "^3.0.0",

View File

@@ -4,6 +4,7 @@ import { Router } from 'vue-router';
import { App } from 'vue';
export default boot(({ app, router }: { app: App<Element>; router: Router }) => {
document.documentElement.lang="ja-JP";
app.config.errorHandler = (err: any, instance: any, info: string) => {
if (err.response && err.response.status === 401) {
// 認証エラーの場合再ログインする

View File

@@ -0,0 +1,152 @@
<template>
<div class="q-mx-md q-mb-lg">
<div class="q-mb-xs q-ml-md text-primary">アプリ選択</div>
<div class="q-pa-md row" style="border: 1px solid rgba(0, 0, 0, 0.12); border-radius: 4px;">
<div v-if="selField?.app && !showSelectApp">{{ selField.app?.name }}</div>
<q-space />
<div>
<q-btn outline dense label="選 択" padding="none sm" color="primary" @click="() => {
showSelectApp = true;
}"></q-btn>
</div>
</div>
</div>
<div v-if="!showSelectApp && selField.app?.name">
<div>
<div class="row q-mb-md">
<!-- <div class="col"> -->
<div class="q-mb-xs q-ml-md text-primary">フィールド選択</div>
<!-- </div> -->
<q-space />
<!-- <div class="col"> -->
<div class="q-mr-md">
<q-input dense debounce="300" v-model="fieldFilter" placeholder="フィールド検索" clearable>
<template v-slot:before>
<q-icon name="search" />
</template>
</q-input>
</div>
</div>
<div class="row">
<field-select ref="fieldDlg" name="フィールド" :type="selectType" :updateSelects="updateItems"
:appId="selField.app?.id" not_page :filter="fieldFilter"
:selFields="selField.fields"></field-select>
</div>
</div>
</div>
<div style="min-width: 45vw;" v-else>
</div>
<show-dialog v-model:visible="showSelectApp" name="アプリ選択" @close="closeAppDlg">
<template v-slot:toolbar>
<q-input dense debounce="300" v-model="filter" placeholder="検索" clearable>
<template v-slot:before>
<q-icon name="search" />
</template>
</q-input>
</template>
<AppSelectBox ref="appDlg" name="アプリ" type="single" :filter="filter"
:updateExternalSelectAppInfo="updateExternalSelectAppInfo"></AppSelectBox>
</show-dialog>
</template>
<script lang="ts">
import { defineComponent, ref, watchEffect, computed ,reactive} from 'vue';
import ShowDialog from './ShowDialog.vue';
import FieldSelect from './FieldSelect.vue';
import { useFlowEditorStore } from 'stores/flowEditor';
import AppSelectBox from './AppSelectBox.vue';
interface IApp {
id: string,
name: string
}
interface IField {
name: string,
code: string,
type: string
}
interface IAppFields {
app?: IApp,
fields: IField[]
}
export default defineComponent({
inheritAttrs: false,
name: 'AppFieldSelectBox',
components: {
ShowDialog,
FieldSelect,
AppSelectBox,
},
props: {
selectedField: {
type: Object,
required: true
},
selectType: {
type: String,
default: 'single'
},
},
setup(props, { emit }) {
const appDlg = ref();
const fieldDlg = ref();
const showSelectApp = ref(false);
const selField = reactive(props.selectedField);
console.log(props.selectedField);
const store = useFlowEditorStore();
const isSelected = computed(() => {
return selField !== null && typeof selField === 'object' && ('app' in selField)
});
const closeAppDlg = (val: string) => {
if (val == 'OK') {
selField.app = appDlg.value.selected[0];
selField.fields = [];
showSelectApp.value = false;
}
};
const closeFieldDialog = (val: string) => {
if (val == 'OK') {
selField.fields = fieldDlg.value.selected;
}
};
const updateExternalSelectAppInfo = (newAppinfo: IApp) => {
selField.app = newAppinfo
}
const updateItems = (newFields: IField[]) => {
selField.fields = newFields
}
watchEffect(() => {
emit('update:modelValue', selField);
});
return {
appDlg,
fieldDlg,
closeAppDlg,
closeFieldDialog,
showSelectApp,
isSelected,
updateExternalSelectAppInfo,
filter: ref(),
updateItems,
fieldFilter: ref(),
selField
};
}
});
</script>

View File

@@ -21,7 +21,7 @@ import { ref, onMounted, reactive, watchEffect } from 'vue'
import { api } from 'boot/axios';
export default {
name: 'AppSelect',
name: 'AppSelectBox',
props: {
name: String,
type: String,

View File

@@ -1,5 +1,5 @@
<template>
<show-dialog v-model:visible="showflg" name="条件エディタ" @close="closeDg" min-width="60vw" min-height="60vh">
<show-dialog v-model:visible="showflg" name="条件エディタ" @close="closeDg" min-width="50vw" min-height="60vh">
<template v-slot:toolbar>
<q-btn flat round dense icon="more_vert" >
<q-menu auto-close anchor="bottom start">

View File

@@ -6,21 +6,21 @@
{{ selectedObject.name }}
</q-chip>
<q-chip color="info" text-color="white" v-if="isSelected && selectedObject.objectType==='variable'" :dense="true" class="selected-obj">
{{ selectedObject.name }}
{{ selectedObject.name.name }}
</q-chip>
</template>
<template v-slot:append>
<q-icon name="search" class="cursor-pointer" @click="showDg"/>
</template>
</q-field>
<show-dialog v-model:visible="show" name="条件設定項目一覧" @close="closeDg" width="600px">
<show-dialog v-model:visible="show" name="設定項目一覧" @close="closeDg" min-width="400px">
<template v-slot:toolbar>
<q-input dense debounce="200" v-model="filter" placeholder="検索" clearable>
<template v-slot:before>
<q-icon name="search" />
</template>
</q-input>
</template>
</template>
<condition-objects ref="appDg" name="フィールド" type="single" :filter="filter" :appId="store.appInfo?.appId" :vars="vars"></condition-objects>
</show-dialog>
</template>
@@ -88,9 +88,9 @@
.condition-object{
min-width: 200px;
max-height: 40px;
padding: 2px;
margin: 0 2px;
}
.selected-obj{
margin: 0px;
margin: 0 2px;
}
</style>

View File

@@ -66,7 +66,7 @@
</div>
<!-- condition -->
<div @click.stop @keypress.stop v-else >
<div class="row no-wrap items-center">
<div class="row no-wrap items-center q-my-xs">
<ConditionObject v-bind="prop.node" v-model="prop.node.object" class="col-4"></ConditionObject>
<q-select v-model="prop.node.operator" :options="operators" class="operator" :outlined="true" :dense="true"></q-select>
<q-input v-if="!prop.node.object || !('options' in prop.node.object)"
@@ -113,7 +113,7 @@ import { finished } from 'stream';
</div>
</template>
<script lang="ts">
import { defineComponent,ref,reactive, computed } from 'vue';
import { defineComponent,ref,reactive, computed, inject } from 'vue';
import { INode,ConditionTree,GroupNode,ConditionNode, LogicalOperator,Operator,NodeType } from '../../types/Conditions';
import ConditionObject from './ConditionObject.vue';
export default defineComponent( {
@@ -143,12 +143,9 @@ export default defineComponent( {
return opts;
});
const operator = inject('Operator')
const operators =computed(()=>{
const opts=[];
for(const op in Operator){
opts.push(Operator[op as keyof typeof Operator]);
}
return opts;
return operator ? operator : Object.values(Operator);
});
const tree = reactive(props.conditionTree);
@@ -257,12 +254,12 @@ export default defineComponent( {
.condition-value{
min-width: 200px;
max-height: 40px;
padding: 2px;
margin: 0 2px;
}
.operator{
min-width: 150px;
max-height: 40px;
padding: 2px;
margin: 0 2px;
text-align: center;
font-size: 12pt;
}

View File

@@ -19,7 +19,7 @@
<q-tab-panels v-model="tab" animated>
<q-tab-panel name="fields">
<field-list v-model="selected" type="single" :filter="filter" :appId="appId"></field-list>
<field-list v-model="selected" type="single" :filter="filter" :appId="sourceApp ? sourceApp :appId " :fields="sourceFields"></field-list>
</q-tab-panel>
<q-tab-panel name="vars" >
@@ -30,7 +30,7 @@
</div>
</template>
<script lang="ts">
import { ref, onMounted, reactive } from 'vue'
import { ref, onMounted, reactive, inject } from 'vue'
import FieldList from './FieldList.vue';
import VariableList from './VariableList.vue';
@@ -48,10 +48,14 @@ export default {
filter:String
},
setup(props) {
const selected = ref([]);
console.log(selected);
return {
sourceFields : inject('sourceFields'),
sourceApp : inject('sourceApp'),
tab: ref('fields'),
selected: ref([])
selected
}
},

View File

@@ -1,51 +1,51 @@
<template>
<div class="q-pa-md">
<q-table flat bordered :loading="!isLoaded" row-key="name" :selection="type"
:selected="modelValue"
@update:selected="$emit('update:modelValue', $event)"
:filter="filter"
:columns="columns" :rows="rows" />
<q-table flat bordered :loading="!isLoaded" row-key="name" :selection="type" :selected="modelValue"
@update:selected="$emit('update:modelValue', $event)" :filter="filter" :columns="columns" :rows="rows" />
</div>
</template>
<script>
import { ref, onMounted, reactive } from 'vue'
<script lang="ts">
import { useAsyncState } from '@vueuse/core';
import { api } from 'boot/axios';
import { computed } from 'vue';
export default {
name: 'FieldList',
props: {
fields: Array,
name: String,
type: String,
appId: Number,
modelValue:Array,
filter:String
},
emits:[
'update:modelValue'
],
modelValue: Array,
filter: String
},
emits: [
'update:modelValue'
],
setup(props) {
const isLoaded = ref(false);
// const rows = ref([]);
// const isLoaded = ref(false);
const columns = [
{ name: 'name', required: true, label: 'フィールド名', align: 'left', field: row => row.name, sortable: true },
{ name: 'name', required: true, label: 'フィールド名', align: 'left', field: 'name', sortable: true },
{ name: 'code', label: 'フィールドコード', align: 'left', field: 'code', sortable: true },
{ name: 'type', label: 'フィールドタイプ', align: 'left', field: 'type', sortable: true }
]
const rows = reactive([]);
onMounted(async () => {
const res = await api.get('api/v1/appfields', {
params: {
app: props.appId
}
});
let fields = res.data.properties;
console.log(fields);
Object.keys(fields).forEach((key) => {
const fld = fields[key];
rows.push({ name: fld.label, objectType: 'field', ...fld });
});
isLoaded.value = true;
});
const { state : rows, isReady: isLoaded, isLoading } = useAsyncState((args) => {
if (props.fields) {
return props.fields.map(f => ({ name: f.label, objectType: 'field', ...f }));
} else {
return api.get('api/v1/appfields', {
params: {
app: props.appId
}
}).then(res => {
console.log(res);
return Object.values(res.data.properties).map(f => ({ name: f.label, objectType: 'field', ...f }));
});
}
}, [{ name: '', objectType: '', type: '', code: '', label: '' }])
return {
columns,
rows,

View File

@@ -1,7 +1,7 @@
<template>
<!-- <div class="q-pa-md q-gutter-sm" > -->
<q-dialog :model-value="visible" persistent bordered >
<q-card :style="cardStyle" style=" min-width: 40vw; max-width: 80vw; max-height: 95vh;">
<q-card style="min-width: 40vw; max-width: 80vw; max-height: 95vh;" :style="cardStyle">
<q-toolbar class="bg-grey-4">
<q-toolbar-title>{{ name }}</q-toolbar-title>
<q-space></q-space>

View File

@@ -1,43 +1,59 @@
<template>
<div class="q-pa-md">
<q-table flat bordered row-key="name" :selection="type"
:selected="modelValue"
@update:selected="$emit('update:modelValue', $event)"
:columns="columns" :rows="rows" />
<q-table flat bordered row-key="id" :selection="type" :selected="modelValue"
@update:selected="$emit('update:modelValue', $event)" :columns="columns" :rows="rows" />
</div>
</template>
<script lang="ts">
import { ref, reactive, PropType, compile } from 'vue';
import {IActionNode,IActionVariable} from '../types/ActionTypes';
import { PropType, reactive } from 'vue';
import { IActionVariable } from '../types/ActionTypes';
import { v4 as uuidv4 } from 'uuid';
export default {
name: 'VariableList',
props: {
name: String,
type: String,
vars:{
type:Array as PropType<IActionVariable[]>,
reqired:true,
default:()=>[]
vars: {
type: Array as PropType<IActionVariable[]>,
reqired: true,
default: () => []
},
modelValue:Array
modelValue: Array
},
emits:[
'update:modelValue'
],
emits: [
'update:modelValue'
],
setup(props) {
const columns= [
{ name: 'actionName', label: 'アクション名',align: 'left',field: 'actionName',sortable: true},
{ name: 'displayName', label: '変数表示名', align: 'left',field: 'displayName', sortable: true },
{ name: 'name', label: '変数名', align: 'left',field: 'name',required: true, sortable: true }
const variableName = (field) => {
const name = field.name;
return name.name;
}
const columns = [
{ name: 'actionName', label: 'アクション名', align: 'left', field: 'actionName', sortable: true },
{ name: 'displayName', label: '変数表示名', align: 'left', field: 'displayName', sortable: true },
{ name: 'name', label: '変数名', align: 'left', field: variableName, required: true, sortable: true }
];
const rows= props.vars.map((v)=>{
return {objectType:'variable',...v};
const rows = props.vars.flatMap((v) => {
if (v.name.vars && v.name.vars.length > 0) {
return v.name.vars
.filter(o => o.vName && o.logicalOperator && o.field)
.map(o => ({
id: uuidv4(),
objectType: 'variable',
name: { name: `${v.name.name}.${o.vName}` },
actionName: v.name.actionName,
displayName: v.name.displayName
}));
} else {
return [{ objectType: 'variable', ...v }];
}
});
return {
columns,
rows:reactive(rows)
rows: reactive(rows)
}
}
}

View File

@@ -30,7 +30,7 @@
</template>
</q-input>
</template>
<AppSelect ref="appDg" name="アプリ" type="single" :filter="filter"></AppSelect>
<AppSelectBox ref="appDg" name="アプリ" type="single" :filter="filter"></AppSelectBox>
</ShowDialog>
</template>
@@ -38,7 +38,7 @@
import { defineComponent,ref } from 'vue';
import {AppInfo} from '../../types/ActionTypes'
import ShowDialog from '../../components/ShowDialog.vue';
import AppSelect from '../../components/AppSelect.vue';
import AppSelectBox from '../../components/AppSelectBox.vue';
import { useFlowEditorStore } from 'stores/flowEditor';
import { useAuthStore } from 'src/stores/useAuthStore';
export default defineComponent({
@@ -47,7 +47,7 @@ export default defineComponent({
"appSelected"
],
components:{
AppSelect,
AppSelectBox,
ShowDialog
},
setup(props, context) {

View File

@@ -1,48 +1,55 @@
<template>
<!-- <div class="q-pa-md q-gutter-sm"> -->
<q-tree
:nodes="store.eventTree.screens"
node-key="eventId"
children-key="events"
no-connectors
v-model:expanded="store.expandedScreen"
:dense="true"
:ref="tree"
>
<template v-slot:header-EVENT="prop">
<div class="row col items-start no-wrap event-node" @click="onSelected(prop.node)">
<q-icon v-if="prop.node.eventId"
name="play_circle"
:color="prop.node.hasFlow?'green':'grey'"
size="16px" class="q-mr-sm">
</q-icon>
<div class="no-wrap" :class="selectedEvent && prop.node.eventId===selectedEvent.eventId?'selected-node':''">{{ prop.node.label }}</div>
<q-space></q-space>
<!-- <q-icon v-if="prop.node.hasFlow" name="delete" color="negative" size="16px" class="q-mr-sm"></q-icon> -->
<q-tree :nodes="store.eventTree.screens" node-key="eventId" children-key="events" no-connectors
v-model:expanded="store.expandedScreen" :dense="true" :ref="tree">
<template v-slot:header-EVENT="prop">
<div :ref="prop.node.eventId" class="row col items-center no-wrap event-node">
<q-icon v-if="prop.node.eventId" name="play_circle" :color="prop.node.hasFlow ? 'green' : 'grey'" size="16px"
class="q-mr-sm">
</q-icon>
<div class="no-wrap" @click="onSelected(prop.node)"
:class="selectedEvent && prop.node.eventId === selectedEvent.eventId ? 'selected-node' : ''">{{
prop.node.label }}</div>
<q-space></q-space>
<!-- <q-icon v-if="prop.node.hasFlow" name="delete" color="negative" size="16px" class="q-mr-sm"></q-icon> -->
</div>
</template>
<template v-slot:header-CHANGE="prop">
<div class="row col items-center no-wrap event-node">
<div class="no-wrap">{{ prop.node.label }}</div>
<q-space></q-space>
<q-icon name="add_circle" color="primary" size="16px" class="q-mr-sm"
@click="addChangeEvent(prop.node)"></q-icon>
</div>
</template>
<template v-slot:header-DELETABLE="prop">
<div class="row col items-center event-node">
<div class="row col items-center" @click="onSelected(prop.node)">
<q-icon v-if="prop.node.eventId" name="play_circle" :color="prop.node.hasFlow ? 'green' : 'grey'" size="16px"
class="q-mr-sm">
</q-icon>
<div>{{ prop.node.label }}</div>
</div>
</template>
<template v-slot:header-CHANGE="prop" >
<div class="row col items-start no-wrap event-node" >
<div class="no-wrap">{{ prop.node.label }}</div>
<q-space></q-space>
<q-icon name="add_circle" color="primary" size="16px" class="q-mr-sm" @click="addChangeEvent(prop.node)"></q-icon>
<div>
<q-btn class="q-mr-sm delete-btn" flat fab-mini icon="delete_forever" padding="none" color="negative"
@click="deleteEvent(prop.node)"></q-btn>
</div>
</template>
</q-tree>
<show-dialog v-model:visible="showDialog" name="フィールド選択" @close="closeDg" widht="400px">
<field-select ref="appDg" name="フィールド" type="single" :appId="store.appInfo?.appId"></field-select>
</show-dialog>
</div>
</template>
</q-tree>
<show-dialog v-model:visible="showDialog" name="フィールド選択" @close="closeDg">
<field-select ref="appDg" name="フィールド" type="single" :appId="store.appInfo?.appId"></field-select>
</show-dialog>
</template>
<script lang="ts">
import { defineComponent, computed, ref } from 'vue';
import { IKintoneEvent ,IKintoneEventGroup, IKintoneEventNode, kintoneEvent} from '../../types/KintoneEvents';
import { storeToRefs } from 'pinia';
import { QTree, useQuasar } from 'quasar';
import { ActionFlow, RootAction } from 'src/types/ActionTypes';
import { useFlowEditorStore } from 'stores/flowEditor';
import { ActionFlow, ActionNode, RootAction } from 'src/types/ActionTypes';
import ShowDialog from '../ShowDialog.vue';
import { defineComponent, ref } from 'vue';
import { IKintoneEvent, IKintoneEventGroup, IKintoneEventNode } from '../../types/KintoneEvents';
import FieldSelect from '../FieldSelect.vue';
import { QTree } from 'quasar';
import ShowDialog from '../ShowDialog.vue';
export default defineComponent({
name: 'EventTree',
components: {
@@ -50,6 +57,7 @@ export default defineComponent({
FieldSelect,
},
setup(props, context) {
const $q = useQuasar();
const appDg = ref();
const store = useFlowEditorStore();
const showDialog = ref(false);
@@ -58,62 +66,79 @@ export default defineComponent({
// const selectedFlow = store.currentFlow;
// const expanded=ref();
const selectedEvent = ref<IKintoneEvent|null>(null);
const selectedChangeEvent=ref<IKintoneEventGroup|null>(null);
const isFieldChange = (node:IKintoneEventNode)=>{
return node.header=='EVENT' && node.eventId.indexOf(".change.")>-1;
const selectedEvent = ref<IKintoneEvent | null>(null);
const selectedChangeEvent = ref<IKintoneEventGroup | null>(null);
const isFieldChange = (node: IKintoneEventNode) => {
return node.header == 'EVENT' && node.eventId.indexOf(".change.") > -1;
}
//フィールド値変更イベント追加
const closeDg = (val:string) => {
const closeDg = (val: string) => {
if (val == 'OK') {
if(!selectedChangeEvent.value){return;}
if (!selectedChangeEvent.value) { return; }
const field = appDg.value.selected[0];
const eventid = `${selectedChangeEvent.value.eventId}.${field.code}`;
if(store.eventTree.findEventById(eventid)){
if (store.eventTree.findEventById(eventid)) {
return;
}
selectedChangeEvent.value?.events.push(
new kintoneEvent(
field.label,
eventid,
selectedChangeEvent.value.eventId)
);
selectedChangeEvent.value?.events.push({
eventId: eventid,
label: field.name,
parentId: selectedChangeEvent.value.eventId,
header: 'DELETABLE'
});
tree.value?.expanded?.push(selectedChangeEvent.value.eventId);
tree.value?.expandAll();
}
};
const addChangeEvent=(node:IKintoneEventGroup)=>{
if(store.appInfo===undefined){
return;
const addChangeEvent = (node: IKintoneEventGroup) => {
if (store.appInfo === undefined) {
return;
}
selectedChangeEvent.value=node;
showDialog.value=true;
selectedChangeEvent.value = node;
showDialog.value = true;
}
const onSelected=(node:IKintoneEvent)=>{
if(!node.eventId){
return;
}
selectedEvent.value=node;
if(store.appInfo===undefined){
return;
}
const screen = store.eventTree.findEventById(node.parentId);
let flow =store.findFlowByEventId(node.eventId);
let screenName=screen!==null?screen.label:"";
let nodeLabel = node.label;
// if(isFieldChange(node)){
// screenName=nodeLabel;
// nodeLabel=`${node.label}の値を変更したとき`;
// }
if(flow!==undefined && flow!==null ){
store.selectFlow(flow);
}else{
const root = new RootAction(node.eventId,screenName,nodeLabel)
const flow =new ActionFlow(root);
store.flows?.push(flow);
store.selectFlow(flow);
selectedEvent.value.flowData=flow;
}
const deleteEvent = (node: IKintoneEvent) => {
if (!node.eventId) {
return;
}
store.deleteEvent(node);
store.selectFlow(undefined)
$q.notify({
type: 'positive',
caption: "通知",
message: `イベント ${node.label} 削除`
})
}
const onSelected = (node: IKintoneEvent) => {
if (!node.eventId) {
return;
}
selectedEvent.value = node;
if (store.appInfo === undefined) {
return;
}
const screen = store.eventTree.findEventById(node.parentId);
let flow = store.findFlowByEventId(node.eventId);
let screenName = screen !== null ? screen.label : "";
let nodeLabel = node.label;
// if(isFieldChange(node)){
// screenName=nodeLabel;
// nodeLabel=`${node.label}の値を変更したとき`;
// }
if (flow !== undefined && flow !== null) {
store.selectFlow(flow);
} else {
const root = new RootAction(node.eventId, screenName, nodeLabel)
const flow = new ActionFlow(root);
store.flows?.push(flow);
store.selectFlow(flow);
selectedEvent.value.flowData = flow;
}
};
return {
// eventTree,
@@ -125,6 +150,7 @@ export default defineComponent({
onSelected,
selectedEvent,
addChangeEvent,
deleteEvent,
closeDg,
store
}
@@ -132,20 +158,25 @@ export default defineComponent({
});
</script>
<style lang="scss">
.nowrap{
flex-wrap:nowarp;
text-wrap:nowarp;
.nowrap {
flex-wrap: nowarp;
text-wrap: nowarp;
}
.event-node{
cursor:pointer;
.event-node {
cursor: pointer;
}
.selected-node{
.selected-node {
color: $primary;
font-weight: bolder;
}
.event-node:hover{
.event-node:hover {
background-color: $light-blue-1;
}
.delete-btn {
margin-right: 5px;
}
</style>

View File

@@ -205,7 +205,7 @@ export default defineComponent({
*/
const varName =(node:IActionNode)=>{
const prop = node.actionProps.find((prop) => prop.props.name === "verName");
return prop?.props.modelValue;
return prop?.props.modelValue.name;
};
const copyFlow=()=>{
context.emit('copyFlow', props.actionNode);

View File

@@ -18,121 +18,68 @@
<q-separator />
<q-card-section class="q-pa-none q-ma-none">
<div style="">
<div v-if="selectedField.fields && selectedField.fields.length > 0 ">
<q-list bordered>
<q-virtual-scroll style="max-height: 160px;" :items="selectedField.fields" separator v-slot="{ item, index }">
<q-item :key="index" dense clickable >
<q-item-section>
<q-item-label>
{{ item.label }}
</q-item-label>
</q-item-section>
<q-item-section side>
<q-btn round flat size="sm" icon="clear" @click="removeField(index)" />
</q-item-section>
</q-item>
</q-virtual-scroll>
</q-list>
<div v-if="selectedField.fields && selectedField.fields.length > 0">
<q-list bordered>
<q-virtual-scroll style="max-height: 160px;" :items="selectedField.fields" separator
v-slot="{ item, index }">
<q-item :key="index" dense clickable>
<q-item-section>
<q-item-label>
{{ item.label }}
</q-item-label>
</q-item-section>
<q-item-section side>
<q-btn round flat size="sm" icon="clear" @click="removeField(index)" />
</q-item-section>
</q-item>
</q-virtual-scroll>
</q-list>
</div>
<!-- <div v-else class="row q-mt-lg">
</div> -->
</div>
<!-- <q-separator /> -->
</q-card-section>
<q-card-section class="q-px-none q-py-xs" v-if="selectedField.fields && selectedField.fields.length===0">
<q-card-section class="q-px-none q-py-xs" v-if="selectedField.fields && selectedField.fields.length === 0">
<div class="row">
<div class="text-grey text-caption"> {{ $props.placeholder }}</div>
<!-- <q-btn flat color="grey" label="clear" @click="clear" /> -->
</div>
</q-card-section>
</q-card>
</div>
</div>
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeFieldDialog" ref="fieldDlg">
<div class="q-mx-md q-mb-lg">
<div class="q-mb-xs q-ml-md text-primary">アプリ選択</div>
<div class="q-pa-md row" style="border: 1px solid rgba(0, 0, 0, 0.12); border-radius: 4px;">
<div v-if="!showSelectApp && selectedField.app">{{ selectedField.app?.name }}</div>
<q-space />
<div>
<q-btn outline dense label="選 択" padding="none sm" color="primary" @click="() => {
showSelectApp = true;
}"></q-btn>
</div>
</div>
</div>
<div v-if="!showSelectApp && selectedField.app?.name">
<div>
<div class="row q-mb-md">
<!-- <div class="col"> -->
<div class="q-mb-xs q-ml-md text-primary">フィールド選択</div>
<!-- </div> -->
<q-space />
<!-- <div class="col"> -->
<div class="q-mr-md">
<q-input dense debounce="300" v-model="fieldFilter" placeholder="フィールド検索" clearable>
<template v-slot:before>
<q-icon name="search" />
</template>
</q-input>
</div>
</div>
<div class="row">
<field-select ref="fieldDlg" name="フィールド" :type="selectType" :updateSelects="updateItems"
:appId="selectedField.app?.id" not_page :filter="fieldFilter" :selectedFields="selectedField.fields"></field-select>
</div>
</div>
</div>
<div style="min-width: 45vw;" v-else>
</div>
</show-dialog>
<show-dialog v-model:visible="showSelectApp" name="アプリ選択" @close="closeAppDlg">
<template v-slot:toolbar>
<q-input dense debounce="300" v-model="filter" placeholder="検索" clearable>
<template v-slot:before>
<q-icon name="search" />
</template>
</q-input>
</template>
<AppSelect ref="appDlg" name="アプリ" type="single" :filter="filter"
:updateExternalSelectAppInfo="updateExternalSelectAppInfo"></AppSelect>
<AppFieldSelectBox v-model:selectedField="selectedField" :selectType="selectType" />
</show-dialog>
</template>
<script lang="ts">
import { defineComponent, ref, watchEffect, computed } from 'vue';
import { defineComponent, ref, watchEffect } from 'vue';
import AppFieldSelectBox from '../AppFieldSelectBox.vue';
import ShowDialog from '../ShowDialog.vue';
import FieldSelect from '../FieldSelect.vue';
import { useFlowEditorStore } from 'stores/flowEditor';
import AppSelect from '../AppSelect.vue';
interface IApp{
id:string,
name:string
export interface IApp {
id: string,
name: string
}
interface IField {
export interface IField {
name: string,
code: string,
type: string
}
interface IAppFields{
app?:IApp,
fields:IField[]
export interface IAppFields {
app?: IApp,
fields: IField[]
}
export default defineComponent({
inheritAttrs:false,
name: 'FieldInput',
inheritAttrs: false,
name: 'AppFieldSelect',
components: {
ShowDialog,
FieldSelect,
AppSelect,
AppFieldSelectBox
},
props: {
displayName: {
@@ -151,62 +98,30 @@ export default defineComponent({
type: Object,
default: null
},
selectType:{
type:String,
default:'single'
selectType: {
type: String,
default: 'single'
}
},
setup(props, { emit }) {
const appDlg = ref();
const fieldDlg = ref();
const show = ref(false);
const showSelectApp = ref(false);
const selectedField = ref<IAppFields>({
app:undefined,
fields:[]
});
if(props.modelValue && "app" in props.modelValue && "fields" in props.modelValue){
selectedField.value=props.modelValue as IAppFields;
}
const selectedField = ref<IAppFields>({
app: undefined,
fields: []
});
if (props.modelValue && "app" in props.modelValue && "fields" in props.modelValue) {
selectedField.value = props.modelValue as IAppFields;
}
const store = useFlowEditorStore();
const isSelected = computed(() => {
return selectedField.value !== null && typeof selectedField.value === 'object' && ('app' in selectedField.value)
});
const showDg = () => {
show.value = true;
};
const clear = () => {
selectedField.value ={
fields:[]
} ;
selectedField.value = {
fields: []
};
}
const closeAppDlg = (val: string) => {
if (val == 'OK') {
selectedField.value.app = appDlg.value.selected[0];
selectedField.value.fields=[];
showSelectApp.value=false;
}
};
const closeFieldDialog=(val:string)=>{
if (val == 'OK') {
selectedField.value.fields = fieldDlg.value.selected;
}
};
const updateExternalSelectAppInfo = (newAppinfo:IApp) => {
// selectedField.value.app = newAppinfo
}
const updateItems = (newFields:IField[]) => {
// selectedField.value.fields = newFields
}
const removeField=(index:number)=>{
selectedField.value.fields.splice(index,1);
const removeField = (index: number) => {
selectedField.value.fields.splice(index, 1);
}
watchEffect(() => {
emit('update:modelValue', selectedField.value);
@@ -214,21 +129,11 @@ export default defineComponent({
return {
store,
appDlg,
fieldDlg,
show,
showDg,
closeAppDlg,
closeFieldDialog,
showDg: () => { show.value = true },
selectedField,
showSelectApp,
isSelected,
updateExternalSelectAppInfo,
filter: ref(),
updateItems,
clear,
fieldFilter: ref(),
removeField
removeField,
};
}
});

View File

@@ -0,0 +1,93 @@
<template>
<div>
<q-field :label="displayName" labelColor="primary" stack-label>
<template v-slot:control>
<q-card flat class="full-width">
<q-card-actions vertical>
<q-btn color="grey-3" text-color="black" @click="() => { dgIsShow = true }">アプリ選択</q-btn>
</q-card-actions>
<q-card-section class="text-caption">
<div v-if="selectedField.app.name">
{{ selectedField.app.name }}
</div>
<div v-else>{{ placeholder }}</div>
</q-card-section>
</q-card>
</template>
</q-field>
</div>
<ShowDialog v-model:visible="dgIsShow" name="アプリ選択" @close="closeDg" min-width="50vw" min-height="50vh">
<template v-slot:toolbar>
<q-input dense debounce="300" v-model="filter" placeholder="検索" clearable>
<template v-slot:before>
<q-icon name="search" />
</template>
</q-input>
</template>
<AppSelectBox ref="appDg" name="アプリ" type="single" :filter="filter"></AppSelectBox>
</ShowDialog>
</template>
<script lang="ts">
import { computed, defineComponent, reactive, ref, watchEffect } from 'vue';
import ShowDialog from '../ShowDialog.vue';
import AppSelectBox from '../AppSelectBox.vue';
export default defineComponent({
inheritAttrs: false,
name: 'AppSelect',
components: {
ShowDialog,
AppSelectBox
},
props: {
context: {
type: Array<Props>,
default: '',
},
displayName: {
type: String,
default: '',
},
name: {
type: String,
default: '',
},
placeholder: {
type: String,
default: '',
},
modelValue: {
type: Object,
default: null
}
},
setup(props, { emit }) {
const appDg = ref()
const dgIsShow = ref(false)
const selectedField = props.modelValue && props.modelValue.app ? props.modelValue : reactive({app:{}});
const closeDg = (state: string) => {
dgIsShow.value = false;
if (state == 'OK') {
selectedField.app = appDg.value.selected[0];
}
};
console.log(selectedField);
watchEffect(() => {
emit('update:modelValue', selectedField);
});
return {
filter: ref(''),
dgIsShow,
appDg,
closeDg,
selectedField
};
}
});
</script>

View File

@@ -1,12 +1,13 @@
<template>
<div v-bind="$attrs">
<q-field v-model="tree" :label="displayName" labelColor="primary" stack-label >
<template v-slot:control >
<q-field v-model="tree" :label="displayName" labelColor="primary" stack-label>
<template v-slot:control>
<q-card flat class="full-width">
<q-card-actions vertical>
<q-btn color="grey-3" text-color="black" @click="showDg()">クリックで設定{{ isSetted?'設定済み':'未設定' }}</q-btn>
<q-btn color="grey-3" text-color="black" :disable="btnDisable" @click="showDg()">クリックで設定{{ isSetted ?
'設定済み' : '未設定' }}</q-btn>
</q-card-actions>
<q-card-section class="text-caption" >
<q-card-section class="text-caption">
<div v-if="!isSetted">{{ placeholder }}</div>
<div v-else>{{ conditionString }}</div>
</q-card-section>
@@ -17,82 +18,148 @@
</div>
</template>
<script lang="ts">
import { defineComponent, ref ,watchEffect,computed,reactive} from 'vue';
import { ConditionTree,GroupNode,ConditionNode,LogicalOperator,Operator } from 'app/src/types/Conditions';
import ConditionEditor from '../ConditionEditor/ConditionEditor.vue'
export default defineComponent({
name: 'FieldInput',
inheritAttrs:false,
components: {
ConditionEditor
},
props: {
displayName:{
type: String,
default: '',
},
name:{
type: String,
default: '',
},
placeholder: {
type: String,
default: '',
},
hint:{
type: String,
default: '',
},
modelValue: {
type: String,
default: null
},
},
setup(props, { emit }) {
const appDg = ref();
const show = ref(false);
const tree = reactive(new ConditionTree());
if(props.modelValue && props.modelValue!==''){
tree.fromJson(props.modelValue);
}else{
const newNode = new ConditionNode({},Operator.Equal,'',tree.root);
tree.addNode(tree.root,newNode);
<script lang="ts">
import { ConditionNode, ConditionTree, Operator, OperatorListItem } from 'app/src/types/Conditions';
import { computed, defineComponent, provide, reactive, ref, watchEffect } from 'vue';
import ConditionEditor from '../ConditionEditor/ConditionEditor.vue';
type Props = {
props?: {
name: string;
modelValue?: {
app: {
id: string;
name: string;
},
fields: {
type: string;
label: string;
code: string;
}[]
}
}
};
export default defineComponent({
name: 'FieldInput',
inheritAttrs: false,
components: {
ConditionEditor
},
props: {
context: {
type: Array<Props>,
default: '',
},
displayName: {
type: String,
default: '',
},
name: {
type: String,
default: '',
},
placeholder: {
type: String,
default: '',
},
hint: {
type: String,
default: '',
},
modelValue: {
type: String,
default: null
},
sourceType: {
type: String,
default: 'field'
},
onlySourceSelect: {
type: Boolean,
default: false
},
operatorList: {
type: Array,
}
},
setup(props, { emit }) {
const source = props.context.find(element => element?.props?.name === 'sources')
if (source) {
if (props.sourceType === 'field') {
provide('sourceFields', computed(() => source.props?.modelValue?.fields ?? []));
} else if (props.sourceType === 'app') {
provide('sourceApp', computed(() => source.props?.modelValue?.app?.id));
}
}
provide('Operator', props.operatorList);
const btnDisable = computed(() => {
const onlySourceSelect = props.onlySourceSelect;
if (!onlySourceSelect) {
return false;
}
const isSetted=ref(props.modelValue && props.modelValue!=='');
if (props.sourceType === 'field') {
return source?.props?.modelValue?.fields?.length ?? 0 > 0;
} else if (props.sourceType === 'app') {
return source?.props?.modelValue?.app?.id ? false : true
}
return true;
})
const conditionString = computed(()=>{
return tree.buildConditionString(tree.root);
});
const appDg = ref();
const show = ref(false);
const tree = reactive(new ConditionTree());
if (props.modelValue && props.modelValue !== '') {
tree.fromJson(props.modelValue);
} else {
const newNode = new ConditionNode({}, (props.operatorList && props.operatorList.length > 0) ? props.operatorList[0] as OperatorListItem : Operator.Equal, '', tree.root);
tree.addNode(tree.root, newNode);
}
const showDg = () => {
show.value = true;
};
const isSetted = ref(props.modelValue && props.modelValue !== '');
const onClosed = (val:string) => {
if (val == 'OK') {
const conditionJson = tree.toJson();
isSetted.value=true;
emit('update:modelValue', conditionJson);
}
};
const conditionString = computed(() => {
return tree.buildConditionString(tree.root);
});
watchEffect(() => {
const showDg = () => {
show.value = true;
};
const onClosed = (val: string) => {
if (val == 'OK') {
isSetted.value = true;
tree.setQuery(tree.buildConditionQueryString(tree.root));
const conditionJson = tree.toJson();
emit('update:modelValue', conditionJson);
});
}
};
return {
appDg,
isSetted,
show,
showDg,
onClosed,
tree,
conditionString
};
}
});
</script>
watchEffect(() => {
tree.setQuery(tree.buildConditionQueryString(tree.root));
const conditionJson = tree.toJson();
emit('update:modelValue', conditionJson);
});
return {
appDg,
isSetted,
show,
showDg,
onClosed,
tree,
conditionString,
btnDisable
};
}
});
</script>

View File

@@ -0,0 +1,202 @@
<template>
<div>
<q-field :label="displayName" labelColor="primary" stack-label>
<template v-slot:control>
<q-card flat class="full-width">
<q-card-actions vertical>
<q-btn color="grey-3" text-color="black" :disable="btnDisable"
@click="() => { dgIsShow = true }">クリックで設定</q-btn>
</q-card-actions>
<q-card-section class="text-caption">
<div v-if="mappingObjectsInputDisplay && mappingObjectsInputDisplay.length > 0">
<div v-for="(item) in mappingObjectsInputDisplay" :key="item">{{ item }}</div>
</div>
<div v-else>{{ placeholder }}</div>
</q-card-section>
</q-card>
</template>
</q-field>
<show-dialog v-model:visible="dgIsShow" name="データマッピング" @close="closeDg" min-width="50vw" min-height="60vh">
<div class="q-mx-md">
<div class="row q-col-gutter-x-xs flex-center">
<div class="col-5">
<div class="q-mx-xs">From</div>
</div>
<div class="col-1">
</div>
<div class="col-5">
<div class="q-mx-xs">To</div>
</div>
<div class="col-1"><q-btn flat round dense icon="add" size="sm" @click="addMappingObject" />
</div>
</div>
<div class="q-my-sm" v-for="(item, index) in mappingProps" :key="item.id">
<div class="row q-col-gutter-x-xs flex-center">
<div class="col-5">
<ConditionObject v-model="item.from" />
</div>
<div class="col-1">
</div>
<div class="col-5">
<q-field v-model="item.vName" type="text" outlined dense>
<template v-slot:append>
<q-icon name="search" class="cursor-pointer"
@click="() => { mappingProps[index].to.isDialogVisible = true }" />
</template>
<template v-slot:control>
<div class="self-center full-width no-outline" tabindex="0"
v-if="item.to.app?.name && item.to.fields?.length > 0 && item.to.fields[0].label">
{{ `${item.to.app?.name} : ${item.to.fields[0].label}` }}
</div>
</template>
</q-field>
</div>
<div class="col-1">
<q-btn flat round dense icon="delete" size="sm" @click="() => deleteMappingObject(index)" />
</div>
</div>
<show-dialog v-model:visible="mappingProps[index].to.isDialogVisible" name="フィールド一覧"
@close="closeToDg" ref="fieldDlg">
<FieldSelect v-if="onlySourceSelect" ref="fieldDlg" name="フィールド" :appId="sourceAppId" not_page
:selectedFields="mappingProps[index].to.fields"
:updateSelects="(fields) => { mappingProps[index].to.fields = fields; mappingProps[index].to.app = sourceApp }">
</FieldSelect>
<AppFieldSelectBox v-else v-model:selectedField="mappingProps[index].to" />
</show-dialog>
</div>
</div>
</show-dialog>
</div>
</template>
<script lang="ts">
import { v4 as uuidv4 } from 'uuid';
import { computed, defineComponent, reactive, ref, watchEffect } from 'vue';
import ConditionObject from '../ConditionEditor/ConditionObject.vue';
import ShowDialog from '../ShowDialog.vue';
import AppFieldSelectBox from '../AppFieldSelectBox.vue';
import FieldSelect from '../FieldSelect.vue';
import IAppFields from './AppFieldSelect.vue';
type Props = {
props?: {
name: string;
modelValue?: {
app: {
id: string;
name: string;
}
}
}
};
type ValueType = {
id: string;
from: object;
to: typeof IAppFields & {
isDialogVisible: boolean;
};
}
const defaultMappingProp = () => ({ id: uuidv4(), to: { app: {}, fields: [], isDialogVisible: false } });
export default defineComponent({
name: 'DataMapping',
inheritAttrs: false,
components: {
ShowDialog,
ConditionObject,
AppFieldSelectBox,
FieldSelect
},
props: {
context: {
type: Array<Props>,
default: '',
},
displayName: {
type: String,
default: '',
},
name: {
type: String,
default: '',
},
modelValue: {
type: Object as () => ValueType[],
},
placeholder: {
type: String,
default: '',
},
onlySourceSelect: {
type: Boolean,
default: false
}
},
setup(props, { emit }) {
const closeDg = () => {
emit('update:modelValue', mappingProps
);
}
const closeToDg = () => {
emit('update:modelValue', mappingProps
);
}
const mappingProps: ValueType[] = props.modelValue
? props.modelValue
: reactive([defaultMappingProp()]);
const deleteMappingObject = (index: number) => mappingProps.length === 1
? mappingProps.splice(0, mappingProps.length, defaultMappingProp())
: mappingProps.splice(index, 1);
const mappingObjectsInputDisplay = computed(() =>
mappingProps ?
mappingProps
.filter(item => item.from?.name && item.to.fields?.length > 0)
.map(item => {
const name = typeof item.from?.name === 'string'
? item.from.name
: item.from?.name.name;
return `[${name}] - (${item.to.app?.name} : ${item.to.fields[0].label})`;
})
: []
);
const source = props.context.find(element => element?.props?.name === 'sources')
const sourceApp = computed(() => source?.props?.modelValue?.app);
const sourceAppId = computed(() => sourceApp.value?.id);
const btnDisable = computed(() => props.onlySourceSelect ? !(source?.props?.modelValue?.app?.id) : false);
//集計処理方法
watchEffect(() => {
emit('update:modelValue', mappingProps);
});
return {
uuidv4,
dgIsShow: ref(false),
closeDg,
toDgIsShow: ref(false),
closeToDg,
mappingProps,
addMappingObject: () => mappingProps.push(defaultMappingProp()),
deleteMappingObject,
mappingObjectsInputDisplay,
sourceApp,
sourceAppId,
btnDisable
};
},
});
</script>
<style lang="scss"></style>

View File

@@ -0,0 +1,229 @@
<template>
<div>
<q-field :label="displayName" labelColor="primary" stack-label>
<template v-slot:control>
<q-card flat class="full-width">
<q-card-actions vertical>
<q-btn color="grey-3" text-color="black" @click="() => { dgIsShow = true }">クリックで設定</q-btn>
</q-card-actions>
<q-card-section class="text-caption">
<div v-if="processingObjectsInputDisplay && processingObjectsInputDisplay.length>0">
<div v-for="(item) in processingObjectsInputDisplay" :key="item">{{ item }}</div>
</div>
<div v-else>{{ placeholder }}</div>
</q-card-section>
</q-card>
</template>
</q-field>
<show-dialog v-model:visible="dgIsShow" name="集計処理" @close="closeDg" min-width="50vw" min-height="60vh">
<div class="q-mx-md q-mb-md">
<q-input v-model="processingProps.name" type="text" label-color="primary" label="集計結果の変数名"
placeholder="集計結果を格納する変数名を入力してください" stack-label />
</div>
<div class="q-mx-md">
<div class="row q-col-gutter-x-xs flex-center">
<div class="col-5">
<div class="q-mx-xs">データソース</div>
</div>
<div class="col-2">
<div class="q-mx-xs">集計計算</div>
</div>
<div class="col-4">
<div class="q-mx-xs">集計結果変数名</div>
</div>
<div class="col-1"><q-btn flat round dense icon="add" size="sm" @click="addProcessingObject" />
</div>
</div>
<div class="q-my-sm" v-for="(item, index) in processingObjects" :key="item.id">
<div class="row q-col-gutter-x-xs flex-center">
<div class="col-5">
<ConditionObject v-model="item.field" />
</div>
<div class="col-2">
<q-select v-model="item.logicalOperator" :options="logicalOperators" outlined dense></q-select>
</div>
<div class="col-4">
<q-input v-model="item.vName" type="text" outlined dense />
</div>
<div class="col-1">
<q-btn flat round dense icon="delete" size="sm" @click="() => deleteProcessingObject(index)" />
</div>
</div>
</div>
</div>
</show-dialog>
</div>
</template>
<script lang="ts">
import { v4 as uuidv4 } from 'uuid';
import { computed, defineComponent, provide, reactive, ref, watchEffect } from 'vue';
import ConditionObject from '../ConditionEditor/ConditionObject.vue';
import ShowDialog from '../ShowDialog.vue';
type Props = {
props?: {
name: string;
modelValue?: {
fields: {
type: string;
label: string;
code: string;
}[]
} | string
}
};
type ProcessingObjectType = {
field?: {
name: string | {
name: string;
};
objectType: string;
type: string;
code: string;
label: string;
noLabel: boolean;
};
logicalOperator?: string;
vName?: string;
id: string;
}
type ValueType = {
name: string;
actionName: string,
displayName: string,
vars: ProcessingObjectType[];
}
export default defineComponent({
name: 'DataProcessing',
inheritAttrs: false,
components: {
ShowDialog,
ConditionObject,
},
props: {
context: {
type: Array<Props>,
default: '',
},
displayName: {
type: String,
default: '',
},
name: {
type: String,
default: '',
},
modelValue: {
type: Object as () => ValueType,
},
placeholder: {
type: String,
default: '',
},
},
setup(props, { emit }) {
const source = props.context.find(element => element?.props?.name === 'sources')
if (source) {
provide('sourceFields', computed(() => {
const modelValue = source.props?.modelValue;
if (modelValue && typeof modelValue !== 'string') {
return modelValue.fields;
}
return null;
}));
}
const actionName = props.context.find(element => element?.props?.name === 'displayName')
const processingProps: ValueType = props.modelValue && props.modelValue.vars
? props.modelValue
: reactive({
name: '',
actionName: actionName?.props?.modelValue as string,
displayName: '結果(戻り値)',
vars: [{ id: uuidv4() }]
});
const closeDg = () => {
emit('update:modelValue', processingProps
);
}
const processingObjects = processingProps.vars;
const deleteProcessingObject = (index: number) => processingObjects.length === 1
? processingObjects.splice(0, processingObjects.length, { id: uuidv4() })
: processingObjects.splice(index, 1);
const processingObjectsInputDisplay = computed(() =>
processingObjects ?
processingObjects
.filter(item => item.field && item.logicalOperator && item.vName)
.map(item => {
const name = typeof item.field?.name === 'string'
? item.field.name
: item.field?.name.name;
return item.logicalOperator.operator!==''?
`${processingProps.name}.${item.vName} = ${item.logicalOperator.operator}(${name})`
:`${processingProps.name}.${item.vName} = ${name}`
})
: []
);
//集計処理方法
const logicalOperators = ref([
{
"operator": "",
"label": "なし"
},
{
"operator": "SUM",
"label": "合計"
},
{
"operator": "AVG",
"label": "平均"
},
{
"operator": "MAX",
"label": "最大値"
},
{
"operator": "MIN",
"label": "最小値"
},
{
"operator": "COUNT",
"label": "カウント"
},
{
"operator": "FIRST",
"label": "最初の値"
}
]);
watchEffect(() => {
emit('update:modelValue', processingProps);
});
return {
uuidv4,
dgIsShow: ref(false),
closeDg,
processingObjects,
processingProps,
addProcessingObject: () => processingObjects.push({ id: uuidv4() }),
deleteProcessingObject,
logicalOperators,
processingObjectsInputDisplay,
};
},
});
</script>
<style lang="scss"></style>

View File

@@ -1,10 +1,7 @@
<template>
<div v-bind="$attrs">
<q-input :label="displayName" v-model="inputValue" label-color="primary"
:placeholder="placeholder" stack-label
:rules="rulesExp"
:maxlength="maxLength"
>
<q-input :label="displayName" v-model="inputValue" label-color="primary" :placeholder="placeholder" stack-label
:rules="rulesExp" :maxlength="maxLength">
<template v-slot:append v-if="hint !== ''">
<q-icon name="help" size="22px" color="blue-8">
<q-tooltip class="bg-yellow-2 text-black shadow-4" anchor="bottom right">
@@ -18,7 +15,7 @@
<script lang="ts">
import { kMaxLength } from 'buffer';
import { defineComponent, ref, watchEffect } from 'vue';
import { defineComponent, ref, watchEffect, computed } from 'vue';
export default defineComponent({
name: 'InputText',
@@ -40,27 +37,50 @@ export default defineComponent({
type: String,
default: '',
},
maxLength:{
maxLength: {
type: Number,
default:undefined
default: undefined
},
//例:[val=>!!val ||'入力してください']
rules:{
type:String,
default:undefined
rules: {
type: String,
default: undefined
},
modelValue: {
type: String,
// type: Any,
default: '',
},
},
setup(props, { emit }) {
const inputValue = ref(props.modelValue);
const rulesExp = props.rules===undefined?null : eval(props.rules);
watchEffect(() => {
emit('update:modelValue', inputValue.value);
const inputValue = computed({
get: () => {
if (props.modelValue !== null && typeof props.modelValue === 'object' && 'name' in props.modelValue) {
return props.modelValue.name;
} else {
return props.modelValue;
}
},
set: (val) => {
if (props.name === 'verName') {
// return props.modelValue.name;
emit('update:modelValue', { name: val });
} else {
emit('update:modelValue', val);
}
},
});
// const inputValue = ref(props.modelValue);
const rulesExp = props.rules === undefined ? null : eval(props.rules);
// const finalValue = computed(() => {
// return props.name !== 'verName' ? inputValue.value : {
// name: inputValue.value,
// };
// });
// watchEffect(() => {
// emit('update:modelValue', finalValue);
// });
return {
inputValue,

View File

@@ -1,7 +1,7 @@
<template>
<div>
<div v-for="(item, index) in properties" :key="index" >
<component :is="item.component" v-bind="item.props" :connectProps="connectProps(item.props)" v-model="item.props.modelValue"></component>
<component :is="item.component" v-bind="item.props" :context="properties" :connectProps="connectProps(item.props)" v-model="item.props.modelValue"></component>
</div>
</div>
</template>
@@ -21,6 +21,9 @@ import ConditionInput from '../right/ConditionInput.vue';
import EventSetter from '../right/EventSetter.vue';
import ColorPicker from './ColorPicker.vue';
import NumInput from './NumInput.vue';
import DataProcessing from './DataProcessing.vue';
import DataMapping from './DataMapping.vue';
import AppSelect from './AppSelect.vue';
import { IActionNode,IActionProperty,IProp } from 'src/types/ActionTypes';
export default defineComponent({
@@ -35,7 +38,10 @@ export default defineComponent({
ConditionInput,
EventSetter,
ColorPicker,
NumInput
NumInput,
DataProcessing,
DataMapping,
AppSelect
},
props: {
nodeProps: {
@@ -50,7 +56,7 @@ export default defineComponent({
setup(props, context) {
const properties=ref(props.nodeProps);
const connectProps=(props:IProp)=>{
const connProps:any={};
const connProps:any={context:properties};
if(props && "connectProps" in props && props.connectProps!=undefined){
for(let connProp of props.connectProps){
let targetProp = properties.value.find((prop)=>prop.props.name===connProp.propName);

View File

@@ -1,11 +1,12 @@
<template>
<div v-bind="$attrs">
<q-select v-model="selectedValue" :label="displayName" :options="options"/>
<q-select v-model="selectedValue" :use-chips="multiple" :label="displayName" label-color="primary" :options="options" stack-label
:multiple="multiple"/>
</div>
</template>
<script lang="ts">
import { defineComponent,ref,watchEffect } from 'vue';
import { defineComponent,ref,watchEffect,computed } from 'vue';
export default defineComponent({
name: 'SelectBox',
@@ -23,20 +24,27 @@ export default defineComponent({
type: Array,
required: true,
},
selectType:{
type:String,
default:'',
},
modelValue: {
type: String,
default: '',
type: Object,
default: null,
},
},
setup(props, { emit }) {
const selectedValue = ref(props.modelValue);
const multiple = computed(()=>{
return props.selectType==='multiple'
});
watchEffect(() => {
emit('update:modelValue', selectedValue.value);
});
return {
selectedValue
selectedValue,
multiple
};
},
});

View File

@@ -1,44 +1,49 @@
import { api } from 'boot/axios';
import { ActionFlow } from 'src/types/ActionTypes';
export class FlowCtrl
{
async getFlows(appId:string):Promise<ActionFlow[]>
{
const flows:ActionFlow[]=[];
try{
const result = await api.get(`api/flows/${appId}`);
//console.info(result.data);
if(!result.data || !Array.isArray(result.data)){
return [];
}
for(const flow of result.data){
flows.push(ActionFlow.fromJSON(flow.content));
}
return flows;
}catch(error){
console.error(error);
return flows;
export class FlowCtrl {
async getFlows(appId: string): Promise<ActionFlow[]> {
const flows: ActionFlow[] = [];
try {
const result = await api.get(`api/flows/${appId}`);
//console.info(result.data);
if (!result.data || !Array.isArray(result.data)) {
return [];
}
for (const flow of result.data) {
flows.push(ActionFlow.fromJSON(flow.content));
}
return flows;
} catch (error) {
console.error(error);
return flows;
}
}
async SaveFlow(jsonData:any):Promise<boolean>
{
const result = await api.post('api/flow',jsonData);
console.info(result.data)
return true;
async SaveFlow(jsonData: any): Promise<boolean> {
const result = await api.post('api/flow', jsonData);
console.info(result.data);
return true;
}
/**
* フローを更新する
* @param jsonData
* @returns
*/
async UpdateFlow(jsonData:any):Promise<boolean>
{
const result = await api.put('api/flow/' + jsonData.flowid,jsonData);
console.info(result.data)
async UpdateFlow(jsonData: any): Promise<boolean> {
const result = await api.put('api/flow/' + jsonData.flowid, jsonData);
console.info(result.data);
return true;
}
/**
* フローを消去する
* @param flowId
* @returns
*/
async DeleteFlow(flowId: string): Promise<boolean> {
const result = await api.delete('api/flow/' + flowId);
console.info(result.data);
return true;
}
/**
@@ -46,12 +51,9 @@ export class FlowCtrl
* @param appid
* @returns
*/
async depoly(appid:string):Promise<boolean>
{
async depoly(appid: string): Promise<boolean> {
const result = await api.post(`api/v1/createjstokintone?app=${appid}`);
console.info(result.data);
return true;
}
}

View File

@@ -24,7 +24,7 @@
<q-btn :label="model+'選択'" color="primary" @click="showDg()" />
<show-dialog v-model:visible="show" :name="model" @close="closeDg" width="400px">
<template v-if="model=='アプリ'">
<app-select ref="appDg" :name="model" type="single"></app-select>
<app-select-box ref="appDg" :name="model" type="single"></app-select-box>
</template>
<template v-if="model=='フィールド'">
<field-select ref="appDg" :name="model" type="multiple" :appId="1"></field-select>
@@ -42,7 +42,7 @@
<script setup lang="ts">
import ShowDialog from 'components/ShowDialog.vue';
import AppSelect from 'components/AppSelect.vue';
import AppSelectBox from 'components/AppSelectBox.vue';
import FieldSelect from 'components/FieldSelect.vue';
import ActionSelect from 'components/ActionSelect.vue';
import { ref } from 'vue'

View File

@@ -1,118 +1,142 @@
import { defineStore } from 'pinia';
import { AppInfo ,IActionFlow, IActionNode} from 'src/types/ActionTypes';
import { IKintoneEvent,KintoneEventManager } from 'src/types/KintoneEvents';
import {FlowCtrl } from '../control/flowctrl';
import { AppInfo, IActionFlow, IActionNode } from 'src/types/ActionTypes';
import { IKintoneEvent, KintoneEventManager } from 'src/types/KintoneEvents';
import { FlowCtrl } from '../control/flowctrl';
export interface FlowEditorState{
flowNames1:string;
appInfo?:AppInfo;
flows?:IActionFlow[];
selectedFlow?:IActionFlow|undefined;
activeNode:IActionNode|undefined;
eventTree:KintoneEventManager;
selectedEvent:IKintoneEvent|undefined;
expandedScreen:any[];
export interface FlowEditorState {
flowNames1: string;
appInfo?: AppInfo;
flows?: IActionFlow[];
selectedFlow?: IActionFlow | undefined;
activeNode: IActionNode | undefined;
eventTree: KintoneEventManager;
selectedEvent: IKintoneEvent | undefined;
expandedScreen: any[];
}
const flowCtrl=new FlowCtrl();
const flowCtrl = new FlowCtrl();
const eventTree = new KintoneEventManager();
export const useFlowEditorStore = defineStore("flowEditor",{
state: ():FlowEditorState => ({
flowNames1: '',
appInfo:undefined,
flows:[],
selectedFlow:undefined,
activeNode:undefined,
eventTree:eventTree,
selectedEvent:undefined,
expandedScreen:[]
}),
export const useFlowEditorStore = defineStore('flowEditor', {
state: (): FlowEditorState => ({
flowNames1: '',
appInfo: undefined,
flows: [],
selectedFlow: undefined,
activeNode: undefined,
eventTree: eventTree,
selectedEvent: undefined,
expandedScreen: [],
}),
getters: {
/**
*
* @returns 現在編集しているフロー
*/
currentFlow():IActionFlow|undefined{
return this.selectedFlow;
currentFlow(): IActionFlow | undefined {
return this.selectedFlow;
},
/**
* KintoneイベントIDから、バンドしているフローを検索する
* @param state
* @returns
*/
findFlowByEventId(state){
return (eventId:string)=>{
return state.flows?.find((flow)=>{
const root=flow.getRoot();
return root?.name===eventId
});
}
}
findFlowByEventId(state) {
return (eventId: string) => {
return state.flows?.find((flow) => {
const root = flow.getRoot();
return root?.name === eventId;
});
};
},
findEventById(state) {
return (eventId: string) => {
return state.eventTree.findEventById(eventId);
};
},
},
actions: {
setFlows(flows:IActionFlow[]){
this.flows=flows;
setFlows(flows: IActionFlow[]) {
this.flows = flows;
},
selectFlow(flow:IActionFlow){
this.selectedFlow=flow;
selectFlow(flow: IActionFlow | undefined) {
this.selectedFlow = flow;
},
setActiveNode(node:IActionNode){
this.activeNode=node;
setActiveNode(node: IActionNode) {
this.activeNode = node;
},
setApp(app:AppInfo){
this.appInfo=app;
setApp(app: AppInfo) {
this.appInfo = app;
},
/**
* DBからフルーを保存する
* @returns
*/
async loadFlow(){
if(this.appInfo===undefined) return;
const actionFlows = await flowCtrl.getFlows(this.appInfo?.appId);
//eventTreeにバンドする
this.eventTree.bindFlows(actionFlows);
if(actionFlows===undefined || actionFlows.length===0){
this.flows=[];
this.selectedFlow=undefined;
return;
}
this.setFlows(actionFlows);
if(actionFlows && actionFlows.length>0){
this.selectFlow(actionFlows[0]);
}
const expandNames = actionFlows.map(flow=>flow.getRoot()?.title);
// const expandName =actionFlows[0].getRoot()?.title;
this.expandedScreen=expandNames;
async loadFlow() {
if (this.appInfo === undefined) return;
const actionFlows = await flowCtrl.getFlows(this.appInfo?.appId);
//eventTreeにバンドする
this.eventTree.bindFlows(actionFlows);
if (actionFlows === undefined || actionFlows.length === 0) {
this.flows = [];
this.selectedFlow = undefined;
return;
}
this.setFlows(actionFlows);
if (actionFlows && actionFlows.length > 0) {
this.selectFlow(actionFlows[0]);
}
const expandNames = actionFlows.map((flow) => flow.getRoot()?.title);
// const expandName =actionFlows[0].getRoot()?.title;
this.expandedScreen = expandNames;
},
/**
* フローをDBに保存及び更新する
*/
async saveFlow(flow:IActionFlow){
const root=flow.getRoot();
const isNew = flow.id==='';
const jsonData={
flowid: isNew ? flow.createNewId():flow.id,
async saveFlow(flow: IActionFlow) {
const root = flow.getRoot();
const isNew = flow.id === '';
const jsonData = {
flowid: isNew ? flow.createNewId() : flow.id,
appid: this.appInfo?.appId,
eventid: root?.name,
name: root?.subTitle,
content: JSON.stringify(flow)
}
content: JSON.stringify(flow),
};
if(isNew){
if (isNew) {
return await flowCtrl.SaveFlow(jsonData);
}else{
} else {
return await flowCtrl.UpdateFlow(jsonData);
}
},
deleteEvent(event: IKintoneEvent) {
const store = useFlowEditorStore();
if (event.flowData) {
const flow = event.flowData;
if (flow.id === '') {
return;
}
flowCtrl.DeleteFlow(flow.id)
eventTree.deleteEvent(event, store);
if(this.flows){
this.flows = this.flows.filter((f) => f.id !== flow.id);
}
} else {
eventTree.deleteEvent(event, store);
}
},
/**
* デプロイする
*/
async deploy():Promise<boolean>{
if(this.appInfo===undefined){
async deploy(): Promise<boolean> {
if (this.appInfo === undefined) {
return false;
}
return await flowCtrl.depoly(this.appInfo?.appId);
}
}
},
},
});

View File

@@ -45,7 +45,16 @@ export interface IActionProperty {
export interface IActionVariable{
actionName:string;
displayName:string;
name:string;
name: {
name:string;
actionName:string;
displayName:string;
vars : {
vName:string;
logicalOperator:string;
field: object;
}[]
};
}
/**
* アクションタイプ定義
@@ -448,6 +457,12 @@ export class ActionFlow implements IActionFlow {
getPrevVarNames(prevNode:IActionNode):IActionVariable[]{
let varNames:IActionVariable[]=[];
if(prevNode.varName!==undefined && prevNode.varName.modelValue){
if(prevNode.varName.modelValue ==='object'){
console.log(prevNode);
}
varNames.unshift({
actionName:prevNode.name,
displayName:prevNode.varName.displayName,

View File

@@ -74,6 +74,11 @@ export class GroupNode implements INode {
}
export type OperatorListItem = {
label: string;
value: string;
}
// 条件式ノード
export class ConditionNode implements INode {
index: number;
@@ -83,13 +88,13 @@ export class ConditionNode implements INode {
return this.parent.logicalOperator;
};
object: any; // 比較元
operator: Operator; // 比較子
operator: Operator | OperatorListItem; // 比較子
value: any;
get header():string{
return 'generic';
}
constructor(object: any, operator: Operator, value: any, parent: GroupNode) {
constructor(object: any, operator: Operator | OperatorListItem, value: any, parent: GroupNode) {
this.index=0;
this.type = NodeType.Condition;
this.object = object;
@@ -113,10 +118,12 @@ export class ConditionNode implements INode {
export class ConditionTree {
root: GroupNode;
maxIndex:number;
queryString:string;
constructor() {
this.maxIndex=0;
this.root = new GroupNode(LogicalOperator.AND, null);
this.queryString='';
}
// ノード追加
@@ -198,12 +205,49 @@ export class ConditionTree {
if(value && typeof value ==='object' && ('label' in value)){
value =condNode.value.label;
}
return `${condNode.object.name} ${condNode.operator} '${value}'`;
return `${typeof condNode.object.name === 'object' ? condNode.object.name.name : condNode.object.name} ${typeof condNode.operator === 'object' ? condNode.operator.label : condNode.operator} '${value}'`;
} else {
return '';
}
}
}
buildConditionQueryString(node:INode){
if (node.type !== NodeType.Condition) {
let conditionString = '';
if(node.type !== NodeType.Root){
conditionString = '(';
}
const groupNode = node as GroupNode;
for (let i = 0; i < groupNode.children.length; i++) {
const childConditionString = this.buildConditionQueryString(groupNode.children[i]);
if (childConditionString !== '') {
conditionString += childConditionString;
if (i < groupNode.children.length - 1) {
conditionString += ` ${groupNode.logicalOperator.toLowerCase()} `;
}
}
}
if(node.type !== NodeType.Root){
conditionString += ')';
}
return conditionString;
} else {
const condNode=node as ConditionNode;
if (condNode.object && condNode.operator ) {
let value=condNode.value;
if(value && typeof value ==='object' && ('label' in value)){
value =condNode.value.label;
}
return `${condNode.object.code} ${typeof condNode.operator === 'object' ? condNode.operator.value : condNode.operator} "${value}"`;
} else {
return '';
}
}
}
/**
*
* @param node ノード移動
@@ -325,7 +369,7 @@ export class ConditionTree {
}
toJson():string{
return JSON.stringify(this.root, (key, value) => {
return JSON.stringify({queryString :this.queryString, ...this.root}, (key, value) => {
if (key === 'parent') {
return value ? value.type : null;
}
@@ -333,4 +377,7 @@ export class ConditionTree {
});
}
setQuery(queryString:string){
this.queryString=queryString;
}
}

View File

@@ -1,9 +1,10 @@
import {IActionFlow} from './ActionTypes';
import { useFlowEditorStore } from 'src/stores/flowEditor';
import { IActionFlow } from './ActionTypes';
export interface IKintoneEventNode {
label: string;
header:string;
eventId:string;
parentId:string;
header: string;
eventId: string;
parentId: string;
}
export interface IKintoneEvent extends IKintoneEventNode {
@@ -15,60 +16,64 @@ export interface IKintoneEventGroup extends IKintoneEventNode {
events: IKintoneEventNode[];
}
export class kintoneEvent implements IKintoneEvent{
export class kintoneEvent implements IKintoneEvent {
eventId: string;
parentId:string;
get hasFlow(): boolean{
return this.flowData!==undefined && this.flowData.actionNodes.length>1
};
parentId: string;
get hasFlow(): boolean {
return this.flowData !== undefined && this.flowData.actionNodes.length > 1;
}
flowData?: IActionFlow | undefined;
label: string;
get header():string{
return "EVENT";
}
constructor(label:string,eventId:string,parentId:string){
this.eventId=eventId;
this.label=label;
this.parentId=parentId;
header = 'EVENT';
constructor(label: string, eventId: string, parentId: string) {
this.eventId = eventId;
this.label = label;
this.parentId = parentId;
}
}
export class kintoneEventGroup implements IKintoneEventGroup{
export class kintoneEventGroup implements IKintoneEventGroup {
eventId: string;
parentId:string;
parentId: string;
label: string;
events: IKintoneEventNode[];
get header():string{
return "EVENTGROUP";
get header(): string {
return 'EVENTGROUP';
}
constructor(eventId:string,label:string,events:IKintoneEventNode[],parentId:string){
this.eventId=eventId;
this.label=label;
this.events=events;
this.parentId=parentId;
constructor(
eventId: string,
label: string,
events: IKintoneEventNode[],
parentId: string
) {
this.eventId = eventId;
this.label = label;
this.events = events;
this.parentId = parentId;
}
}
export class kintoneEventForChange implements IKintoneEventGroup{
export class kintoneEventForChange implements IKintoneEventGroup {
eventId: string;
parentId:string;
parentId: string;
label: string;
events: IKintoneEventNode[];
get header():string{
return "CHANGE";
get header(): string {
return 'CHANGE';
}
constructor(eventId:string,label:string,events:IKintoneEventNode[],parentId:string){
this.eventId=eventId;
this.label=label;
this.events=events;
this.parentId=parentId;
constructor(
eventId: string,
label: string,
events: IKintoneEventNode[],
parentId: string
) {
this.eventId = eventId;
this.label = label;
this.events = events;
this.parentId = parentId;
}
}
export class KintoneEventManager {
public screens: IKintoneEventGroup[];
@@ -76,28 +81,35 @@ export class KintoneEventManager {
this.screens = this.getKintoneEvents();
}
public bindFlows(flows:IActionFlow[]){
this.screens=this.getKintoneEvents();
for (const flow of flows){
const eventId =flow.getRoot()?.name;
if(eventId!==undefined){
public bindFlows(flows: IActionFlow[]) {
this.screens = this.getKintoneEvents();
for (const flow of flows) {
const eventId = flow.getRoot()?.name;
if (eventId !== undefined) {
const eventNode = this.findEventById(eventId);
if(eventNode!==null && eventNode.header==="EVENT"){
const event =eventNode as kintoneEvent;
event.flowData=flow;
}else{
if (eventNode !== null && eventNode.header === 'EVENT') {
const event = eventNode as kintoneEvent;
event.flowData = flow;
} else {
//EventGroupのIDを取得
const lastIndex = eventId.lastIndexOf(".");
const groupId=eventId.substring(0,lastIndex);
const lastIndex = eventId.lastIndexOf('.');
const groupId = eventId.substring(0, lastIndex);
const eventNode = this.findEventById(groupId);
if(eventNode && (eventNode.header==="EVENTGROUP" || eventNode.header==="CHANGE")){
const groupEvent=eventNode as kintoneEventGroup;
const newEvent =new kintoneEvent(
flow.getRoot()?.subTitle || "",
eventId,
groupEvent.parentId
);
newEvent.flowData=flow;
if (
eventNode &&
(eventNode.header === 'EVENTGROUP' || eventNode.header === 'CHANGE')
) {
const groupEvent = eventNode as kintoneEventGroup;
const newEvent = {
label: flow.getRoot()?.subTitle || '',
eventId: eventId,
parentId: groupId,
header: 'DELETABLE',
hasFlow: true,
flowData: flow,
};
groupEvent.events.push(newEvent);
}
}
@@ -106,61 +118,193 @@ export class KintoneEventManager {
}
public findEventById(eventId: string): IKintoneEventNode | null {
const screen=this.findScreen(eventId);
if(screen) {return screen;}
const screen = this.findScreen(eventId);
if (screen) {
return screen;
}
for (const screen of this.screens) {
for (const event of screen.events) {
if (event.eventId === eventId) {
return event;
}
if(event.header==="EVENTGROUP"||event.header==="CHANGE"){
const eventGroup = event as IKintoneEventGroup;
const targetEvent = eventGroup.events.find((ev)=>{
return ev.eventId===eventId;
})
if(targetEvent){
return targetEvent;
}
if (event.eventId === eventId) {
return event;
}
if (event.header === 'EVENTGROUP' || event.header === 'CHANGE') {
const eventGroup = event as IKintoneEventGroup;
const targetEvent = eventGroup.events.find((ev) => {
return ev.eventId === eventId;
});
if (targetEvent) {
return targetEvent;
}
}
}
}
return null;
}
public findScreen(eventId:string):IKintoneEventGroup|undefined{
return this.screens.find(screen=>screen.eventId==eventId);
public findScreen(eventId: string): IKintoneEventGroup | undefined {
return this.screens.find((screen) => screen.eventId == eventId);
}
public getKintoneEvents():IKintoneEventGroup[]{
public deleteEvent(
event: kintoneEvent,
store: ReturnType<typeof useFlowEditorStore>
) {
if (event.header !== 'DELETABLE') {
return;
}
const parent = store.findEventById(event.parentId);
if (parent?.header !== 'CHANGE' && parent?.header !== 'EVENTGROUP') {
return;
}
const realParent = parent as kintoneEventForChange;
const index = realParent.events.findIndex(
(e) => e.eventId === event.eventId
);
if (index !== -1) {
realParent.events.splice(index, 1);
}
}
public getKintoneEvents(): IKintoneEventGroup[] {
return [
new kintoneEventGroup("app.record.create","レコード追加画面",[
new kintoneEvent('レコード追加画面を表示した後','app.record.create.show',"app.record.create"),
new kintoneEvent('保存をクリックしたとき','app.record.create.submit',"app.record.create"),
new kintoneEvent('保存が成功したとき','app.record.create.submit.success',"app.record.create"),
new kintoneEventForChange('app.record.create.change','フィールドの値を変更したとき',[],"app.record.create"),
new kintoneEventGroup('app.record.create.show.customButtonClick','ボタンをクリックした',[],"app.record.create")
],""),
new kintoneEventGroup("app.record.detail","レコード詳細画面",[
new kintoneEvent('レコード詳細画面を表示した後','app.record.detail.show',"app.record.detail"),
new kintoneEvent('レコードを削除するとき','app.record.detail.delete.submit',"app.record.detail"),
new kintoneEvent('プロセス管理のアクションを実行したとき','app.record.detail.process.proceed',"app.record.detail"),
new kintoneEventGroup('app.record.detail.show.customButtonClick','ボタンをクリックした時',[],"app.record.detail"),
],""),
new kintoneEventGroup("app.record.edit","レコード編集画面",[
new kintoneEvent('レコード編集画面を表示した後','app.record.edit.show',"app.record.edit"),
new kintoneEvent('保存をクリックしたとき','app.record.edit.submit',"app.record.edit"),
new kintoneEvent('保存が成功したとき','app.record.edit.submit.success',"app.record.edit"),
new kintoneEventForChange('app.record.edit.change','フィールドの値を変更したとき',[],"app.record.edit"),
new kintoneEventGroup('app.record.edit.show.customButtonClick','ボタンをクリックした時',[],"app.record.edit"),
],""),
new kintoneEventGroup("app.record.index","レコード一覧画面",[
new kintoneEvent('一覧画面を表示した後', 'app.record.index.show',"app.record.index"),
new kintoneEvent('インライン編集を開始したとき','app.record.index.edit.show',"app.record.index"),
new kintoneEvent('インライン編集の【保存】をクリックしたとき','app.record.index.edit.submit',"app.record.index"),
new kintoneEvent('インライン編集の保存が成功したとき', 'app.record.index.edit.submit.success',"app.record.index"),
new kintoneEventForChange('app.record.index.edit.change','インライン編集のフィールド値を変更したとき' ,[],"app.record.index"),
new kintoneEventGroup('app.record.detail.show.customButtonClick','ボタンをクリックした時',[],"app.record.index"),
],"")
new kintoneEventGroup(
'app.record.create',
'レコード追加画面',
[
new kintoneEvent(
'レコード追加画面を表示した',
'app.record.create.show',
'app.record.create'
),
new kintoneEvent(
'保存をクリックしたとき',
'app.record.create.submit',
'app.record.create'
),
new kintoneEvent(
'保存が成功したとき',
'app.record.create.submit.success',
'app.record.create'
),
new kintoneEventForChange(
'app.record.create.change',
'フィールドの値を変更したとき',
[],
'app.record.create'
),
new kintoneEventGroup(
'app.record.create.show.customButtonClick',
'ボタンをクリックした時',
[],
'app.record.create'
),
],
''
),
new kintoneEventGroup(
'app.record.detail',
'レコード詳細画面',
[
new kintoneEvent(
'レコード詳細画面を表示した後',
'app.record.detail.show',
'app.record.detail'
),
new kintoneEvent(
'レコードを削除するとき',
'app.record.detail.delete.submit',
'app.record.detail'
),
new kintoneEvent(
'プロセス管理のアクションを実行したとき',
'app.record.detail.process.proceed',
'app.record.detail'
),
new kintoneEventGroup(
'app.record.detail.show.customButtonClick',
'ボタンをクリックした時',
[],
'app.record.detail'
),
],
''
),
new kintoneEventGroup(
'app.record.edit',
'レコード編集画面',
[
new kintoneEvent(
'レコード編集画面を表示した後',
'app.record.edit.show',
'app.record.edit'
),
new kintoneEvent(
'保存をクリックしたとき',
'app.record.edit.submit',
'app.record.edit'
),
new kintoneEvent(
'保存が成功したとき',
'app.record.edit.submit.success',
'app.record.edit'
),
new kintoneEventForChange(
'app.record.edit.change',
'フィールドの値を変更したとき',
[],
'app.record.edit'
),
new kintoneEventGroup(
'app.record.edit.show.customButtonClick',
'ボタンをクリックした時',
[],
'app.record.edit'
),
],
''
),
new kintoneEventGroup(
'app.record.index',
'レコード一覧画面',
[
new kintoneEvent(
'一覧画面を表示した後',
'app.record.index.show',
'app.record.index'
),
new kintoneEvent(
'インライン編集を開始したとき',
'app.record.index.edit.show',
'app.record.index'
),
new kintoneEvent(
'インライン編集の【保存】をクリックしたとき',
'app.record.index.edit.submit',
'app.record.index'
),
new kintoneEvent(
'インライン編集の保存が成功したとき',
'app.record.index.edit.submit.success',
'app.record.index'
),
new kintoneEventForChange(
'app.record.index.edit.change',
'インライン編集のフィールド値を変更したとき',
[],
'app.record.index'
),
new kintoneEventGroup(
'app.record.detail.show.customButtonClick',
'ボタンをクリックした時',
[],
'app.record.index'
),
],
''
),
];
}
}

10
node_modules/.yarn-integrity generated vendored
View File

@@ -1,10 +0,0 @@
{
"systemParams": "win32-x64-115",
"modulesFolders": [],
"flags": [],
"linkedModules": [],
"topLevelPatterns": [],
"lockfileEntries": {},
"files": [],
"artifacts": {}
}

View File

@@ -0,0 +1,2 @@
VITE_SOURCE_MAP = inline
VITE_PORT = 4173

View File

@@ -0,0 +1,2 @@
VITE_SOURCE_MAP = false
VITE_PORT = 4173

View File

@@ -8,8 +8,7 @@
"name": "kintone-addins",
"version": "0.0.0",
"dependencies": {
"jquery": "^3.7.1",
"yarn": "^1.22.22"
"jquery": "^3.7.1"
},
"devDependencies": {
"@types/jquery": "^3.5.24",
@@ -796,19 +795,6 @@
"optional": true
}
}
},
"node_modules/yarn": {
"version": "1.22.22",
"resolved": "https://registry.npmjs.org/yarn/-/yarn-1.22.22.tgz",
"integrity": "sha512-prL3kGtyG7o9Z9Sv8IPfBNrWTDmXB4Qbes8A9rEzt6wkJV8mUvoirjU0Mp3GGAU06Y0XQyA3/2/RQFVuK7MTfg==",
"hasInstallScript": true,
"bin": {
"yarn": "bin/yarn.js",
"yarnpkg": "bin/yarn.js"
},
"engines": {
"node": ">=4.0.0"
}
}
}
}

View File

@@ -4,22 +4,30 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "tsc && set \"SOURCE_MAP=true\" && vite build && vite preview",
"build": "tsc && vite build && xcopy dist\\*.js ..\\..\\backend\\Temp\\ /E /I /Y",
"build:dev": "tsc && set \"SOURCE_MAP=true\" && vite build && xcopy dist\\*.js ..\\..\\backend\\Temp\\ /E /I /Y",
"preview": "vite preview",
"ngrok":"ngrok http http://localhost:4173/",
"vite":"vite dev"
"dev": "run-p watch server ngrok",
"watch": "vite build --watch --mode dev",
"server": "vite dev --mode dev",
"ngrok": "ngrok http 4173",
"build": "run-s b:production copy:windows",
"build:dev": "run-s b:dev copy:windows",
"build:linux": "run-s b:production copy:linux",
"build:linux-dev": "run-s b:dev copy:linux",
"b:production": "vite build --mode production",
"b:dev": "vite build --mode dev",
"copy:windows": "xcopy dist\\*.js ..\\..\\backend\\Temp\\ /E /I /Y",
"copy:linux": "cp -ur dist/*.js ../../backend/Temp"
},
"devDependencies": {
"@types/jquery": "^3.5.24",
"@types/node": "^20.8.9",
"npm-run-all2": "^6.2.0",
"sass": "^1.69.5",
"typescript": "^5.0.2",
"vite": "^4.4.5"
"vite": "^4.4.5",
"vite-plugin-checker": "^0.6.4",
"vite-plugin-lib-inject-css": "^2.1.1"
},
"dependencies": {
"jquery": "^3.7.1",
"yarn": "^1.22.22"
"jquery": "^3.7.1"
}
}

View File

@@ -0,0 +1,24 @@
.alc-button-normal {
display: inline-block;
box-sizing: border-box;
padding: 0 16px;
margin-left: 16px;
margin-top: 8px;
min-width: 100px;
outline: none;
border: 1px solid #e3e7e8;
background-color: #f7f9fa;
box-shadow: 1px 1px 1px #fff inset;
color: #3498db;
text-align: center;
line-height: 32px;
}
.alc-button-normal:hover {
background-color: #c8d6dd;
box-shadow: none;
cursor: pointer;
}
.alc-button-normal:active {
color: #f7f9fa;
background-color: #54b8eb;
}

View File

@@ -2,6 +2,7 @@
import { actionAddins } from ".";
import { IField, IAction,IActionResult, IActionNode, IActionProperty, IContext } from "../types/ActionTypes";
import { Formatter } from "../util/format";
import "./auto-numbering.css";
declare global {
interface Window { $format: any; }
@@ -84,6 +85,7 @@ export class AutoNumbering implements IAction{
execEval(match:string,expr:string):string{
console.log(match);
// @ts-ignore
return eval(expr);
}

View File

@@ -2,6 +2,8 @@
import { actionAddins } from ".";
import $ from 'jquery';
import { IAction, IActionProperty, IActionNode, IActionResult } from "../types/ActionTypes";
import "./button-add.css";
/**
* ボタン配置属性定義
*/
@@ -51,30 +53,7 @@ export class ButtonAddAction implements IAction {
if(!menuSpace) return result;
if($("style#alc-button-add").length===0){
const css=`
.alc-button-normal {
display: inline-block;
box-sizing: border-box;
padding: 0 16px;
margin-left: 16px;
margin-top: 8px;
min-width: 100px;
outline: none;
border: 1px solid #e3e7e8;
background-color: #f7f9fa;
box-shadow: 1px 1px 1px #fff inset;
color: #3498db;
text-align: center;
line-height: 32px;
}
.alc-button-normal:hover {
background-color: #c8d6dd;
box-shadow: none;
cursor: pointer;
}
.alc-button-normal:active {
color: #f7f9fa;
background-color: #54b8eb;
}`;
`;
const style = $("<style id='alc-button-add'>/<style>");
style.text(css);
$("head").append(style);

View File

@@ -1,75 +0,0 @@
import { actionAddins } from ".";
import { IAction,IActionResult, IActionNode, IActionProperty, IField} from "../types/ActionTypes";
/**
* アクションの属性定義
*/
interface IStrCountCheckProps{
field:IField;//チェックするフィールドの対象
message:string;//エラーメッセージ
strExpression:string;//
}
/**
* 正規表現チェックアクション
*/
export class StrCountCheckAciton implements IAction{
name: string;
actionProps: IActionProperty[];
props:IStrCountCheckProps;
constructor(){
this.name="文字数チェック";
this.actionProps=[];
this.props={
field:{code:''},
message:'',
strExpression:''
}
//アクションを登録する
this.register();
}
/**
* アクションの実行を呼び出す
* @param actionNode
* @param event
* @returns
*/
async process(actionNode:IActionNode,event:any):Promise<IActionResult> {
let result={
canNext:true,
result:false
};
try{
//属性設定を取得する
this.actionProps=actionNode.actionProps;
if (!('field' in actionNode.ActionValue) && !('message' in actionNode.ActionValue) && !('strExpression'in actionNode.ActionValue)) {
return result
}
this.props = actionNode.ActionValue as IStrCountCheckProps;
//条件式の計算結果を取得
const record = event.record;
const value = record[this.props.field.code].value;
const strCheck = this.props.strExpression;
if(value === undefined || value === '' ){
return result;
}else if(strCheck < value.length){
record[this.props.field.code].error = this.props.message;
}else{
result= {
canNext:true,
result:true
}
}
return result;
}catch(error){
event.error=error;
console.error(error);
result.canNext=false;
return result;
}
};
register(): void {
actionAddins[this.name]=this;
}
}
new StrCountCheckAciton();

View File

@@ -0,0 +1,182 @@
import {
IAction,
IActionResult,
IActionNode,
IActionProperty,
IContext,
} from "../types/ActionTypes";
import { actionAddins } from ".";
export type IApp = {
id: string;
name: string;
};
export type IField = {
name: string;
code: string;
type: string;
};
export type IAppFields = {
app?: IApp;
fields: IField[];
};
type ValueType = {
id: string;
from: {
objectType: "variable" | "field";
name: { name: string };
code: string;
};
to: IAppFields & {
isDialogVisible: boolean;
};
};
type Props = { app: IApp; field: ValueType[] };
export class DataMappingAction implements IAction {
name: string;
actionProps: IActionProperty[];
dataMappingProps: Props;
constructor() {
this.name = "DataMapping";
this.actionProps = [];
this.dataMappingProps = {} as Props;
this.register();
}
async process(
prop: IActionNode,
event: any,
context: IContext
): Promise<IActionResult> {
this.initActionProps(prop);
this.initTypedActionProps();
let result = {
canNext: true,
result: "",
} as IActionResult;
try {
for (const item of this.dataMappingProps.field) {
if (item.from.objectType === "variable") {
if (
item.from.name.name &&
item.to.app &&
item.to.fields &&
item.to.fields.length > 0
) {
const value = getValueByPath(
context.variables,
item.from.name.name
);
if (value) {
await kintone.api(
kintone.api.url("/k/v1/record.json", true),
"POST",
{
app: item.to.app.id,
record: {
[item.to.fields[0].code]: {
value: value,
},
},
}
);
}
}
} else if (item.from.objectType === "field") {
if (
item.from.code &&
item.to.app &&
item.to.fields &&
item.to.fields.length > 0
) {
const value = await selectData(
item.to.app.id,
item.to.fields[0].code
);
if (value && value.type === context.record[item.from.code].type) {
await kintone.api(
kintone.api.url("/k/v1/records.json", true),
"POST",
{
app: item.to.app.id,
records: value.value.map((v) => ({
[item.to.fields[0].code]: {
value: v,
},
})),
}
);
}
}
}
}
} catch (error) {
console.error("DataMappingAction error", error);
result.canNext = false;
}
console.log("dataMappingProps", this.dataMappingProps);
return result;
}
private initActionProps(nodes: IActionNode) {
this.actionProps = nodes.actionProps;
}
private initTypedActionProps() {
for (const action of this.actionProps) {
if (action.component === "DataMapping") {
this.dataMappingProps.field = action.props.modelValue as ValueType[];
} else if (action.component === "AppSelect") {
this.dataMappingProps.app = action.props.modelValue.app as IApp;
}
}
}
register(): void {
actionAddins[this.name] = this;
}
}
new DataMappingAction();
const getValueByPath = (obj: any, path: string) => {
return path.split(".").reduce((o, k) => (o || {})[k], obj);
};
type Resp = { records: RespRecordType[] };
type RespRecordType = {
[key: string]: {
type: string;
value: any;
};
};
type Result = {
type: string;
value: any[];
};
const selectData = async (appid: string, field: string): Promise<Result> => {
return kintone
.api(kintone.api.url("/k/v1/records", true), "GET", {
app: appid ?? kintone.app.getId(),
fields: [field],
})
.then((resp: Resp) => {
const result: Result = { type: "", value: [] };
resp.records.forEach((element) => {
for (const [key, value] of Object.entries(element)) {
if (result.type === "") {
result.type = value.type;
}
result.value.push(value.value);
}
});
return result;
});
};

View File

@@ -0,0 +1,280 @@
import {
IAction,
IActionResult,
IActionNode,
IActionProperty,
IContext,
} from "../types/ActionTypes";
import { actionAddins } from ".";
type DataProcessingProps = {
app: {
id: string;
name: string;
};
conditionsQuery: string;
propcessing: {
varRootName: string;
fields: Field[];
};
};
type Field = {
name: string;
code: string;
type: string;
varName: string;
operator: string;
};
export class DataProcessingAction implements IAction {
name: string;
actionProps: IActionProperty[];
dataProcessingProps: DataProcessingProps | null;
constructor() {
this.name = "データ処理";
this.actionProps = [];
this.dataProcessingProps = null;
this.register();
}
async process(
nodes: IActionNode,event: any,context: IContext
): Promise<IActionResult> {
this.initActionProps(nodes);
this.initTypedActionProps();
let result = {
canNext: true,
result: "",
} as IActionResult;
try {
if (!this.dataProcessingProps) {
return result;
}
const data = await selectData(this.dataProcessingProps.conditionsQuery);
console.log("data ", data);
context.variables[this.dataProcessingProps.propcessing.varRootName] =
this.dataProcessingProps.propcessing.fields.reduce((acc, f) => {
const v = calc(f, data);
if (v) {
acc[f.varName] = calc(f, data);
}
return acc;
}, {} as Var);
console.log("context ", context);
return result;
} catch (error) {
console.error(error);
event.error=error;
return result;
}
}
register(): void {
actionAddins[this.name] = this;
}
private initActionProps(nodes: IActionNode) {
this.actionProps = nodes.actionProps;
}
private initTypedActionProps() {
this.dataProcessingProps = {
app: {
id: "",
name: "",
},
conditionsQuery: "",
propcessing: {
varRootName: "",
fields: [],
},
};
for (const action of this.actionProps) {
if (action.component === "AppFieldSelect") {
this.dataProcessingProps.app.id = action.props.modelValue.app.id;
this.dataProcessingProps.app.name = action.props.modelValue.app.name;
} else if (action.component === "DataProcessing") {
this.dataProcessingProps.propcessing.varRootName =
action.props.modelValue.name;
for (const f of action.props.modelValue.vars) {
this.dataProcessingProps.propcessing.fields.push({
name: f.field.name,
code: f.field.code,
type: f.field.type,
varName: f.vName,
operator: f.logicalOperator.operator,
});
}
} else if (action.component === "ConditionInput") {
this.dataProcessingProps.conditionsQuery = JSON.parse(
action.props.modelValue
).queryString;
}
}
}
}
new DataProcessingAction();
const selectData = async (query?: string) => {
return kintone
.api(kintone.api.url("/k/v1/records", true), "GET", {
app: kintone.app.getId(),
query: query,
})
.then((resp: Resp) => {
const result: Result = {};
resp.records.forEach((element) => {
for (const [key, value] of Object.entries(element)) {
if (!result[key]) {
result[key] = { type: value.type, value: [] };
}
result[key].value.push(value.value);
}
});
return result;
});
};
type Resp = { records: RespRecordType[] };
type RespRecordType = {
[key: string]: {
type: string;
value: any;
};
};
type Result = {
[key: string]: {
type: string;
value: any[];
};
};
type Var = {
[key: string]: any;
};
const ERROR_TYPE = "ERROR_TYPE";
const calc = (field: Field, result: Result) => {
const type = typeCheck(field.type);
if (!type) {
return ERROR_TYPE;
}
const fun =
calcFunc[`${type}_${Operator[field.operator as keyof typeof Operator]}`];
if (!fun) {
return ERROR_TYPE;
}
const values = result[field.code].value;
if (!values) {
return null;
}
return fun(values);
};
const typeCheck = (type: string) => {
switch (type) {
case "RECORD_NUMBER":
case "NUMBER":
return CalcType.NUMBER;
case "SINGLE_LINE_TEXT":
case "MULTI_LINE_TEXT":
case "RICH_TEXT":
return CalcType.STRING;
case "DATE":
return CalcType.DATE;
case "TIME":
return CalcType.TIME;
case "DATETIME":
case "UPDATED_TIME":
return CalcType.DATETIME;
default:
return null;
}
};
enum Operator {
SUM = "SUM",
AVG = "AVG",
MAX = "MAX",
MIN = "MIN",
COUNT = "COUNT",
FIRST = "FIRST"
}
enum CalcType {
NUMBER = "number",
STRING = "string",
DATE = "date",
TIME = "time",
DATETIME = "datetime",
}
const calcFunc: Record<string, (value: string[]) => string | null> = {
[`${CalcType.NUMBER}_${Operator.COUNT}`]: (value: string[]) =>
value.length.toString(),
[`${CalcType.STRING}_${Operator.COUNT}`]: (value: string[]) =>
value.length.toString(),
[`${CalcType.DATE}_${Operator.COUNT}`]: (value: string[]) =>
value.length.toString(),
[`${CalcType.TIME}_${Operator.COUNT}`]: (value: string[]) =>
value.length.toString(),
[`${CalcType.DATETIME}_${Operator.COUNT}`]: (value: string[]) =>
value.length.toString(),
[`${CalcType.NUMBER}_${Operator.SUM}`]: (value: string[]) =>
value.reduce((acc, v) => acc + Number(v), 0).toString(),
[`${CalcType.NUMBER}_${Operator.AVG}`]: (value: string[]) =>
(value.reduce((acc, v) => acc + Number(v), 0) / value.length).toString(),
[`${CalcType.NUMBER}_${Operator.MAX}`]: (value: string[]) =>
Math.max(...value.map(Number)).toString(),
[`${CalcType.NUMBER}_${Operator.MIN}`]: (value: string[]) =>
Math.min(...value.map(Number)).toString(),
[`${CalcType.STRING}_${Operator.SUM}`]: (value: string[]) => value.join(" "),
[`${CalcType.DATE}_${Operator.MAX}`]: (value: string[]) =>
value.reduce((maxDate, currentDate) =>
maxDate > currentDate ? maxDate : currentDate
),
[`${CalcType.DATE}_${Operator.MIN}`]: (value: string[]) =>
value.reduce((minDate, currentDate) =>
minDate < currentDate ? minDate : currentDate
),
[`${CalcType.TIME}_${Operator.MAX}`]: (value: string[]) =>
value.reduce((maxTime, currentTime) =>
maxTime > currentTime ? maxTime : currentTime
),
[`${CalcType.TIME}_${Operator.MIN}`]: (value: string[]) =>
value.reduce((minTime, currentTime) =>
minTime < currentTime ? minTime : currentTime
),
[`${CalcType.DATETIME}_${Operator.MAX}`]: (value: string[]) =>
value.reduce((maxDateTime, currentDateTime) =>
new Date(maxDateTime) > new Date(currentDateTime)
? maxDateTime
: currentDateTime
),
[`${CalcType.DATETIME}_${Operator.MIN}`]: (value: string[]) =>
value.reduce((minDateTime, currentDateTime) =>
new Date(minDateTime) < new Date(currentDateTime)
? minDateTime
: currentDateTime
),
[`${CalcType.STRING}_${Operator.FIRST}`]:(value: string[])=>{
return value[0];
}
};

View File

@@ -1,89 +0,0 @@
import { actionAddins } from ".";
import { IAction, IActionResult, IActionNode, IActionProperty, IField } from "../types/ActionTypes";
/**
* アクションの属性定義
*/
interface IMailCheckProps {
field: IField;//チェックするフィールドの対象
message: string;//エラーメッセージ
}
/**
* メールアドレスチェックアクション
*/
export class MailCheckAction implements IAction {
name: string;
actionProps: IActionProperty[];
props: IMailCheckProps;
constructor() {
this.name = "メールアドレスチェック";
this.actionProps = [];
this.props = {
field: { code: '' },
message: '',
}
//アクションを登録する
this.register();
}
/**
* アクションの実行を呼び出す
* @param actionNode
* @param event
* @returns
*/
async process(actionNode: IActionNode, event: any): Promise<IActionResult> {
let result = {
canNext: true,
result: false
};
try {
//属性設定を取得する
this.actionProps = actionNode.actionProps;
const emailAction = this.actionProps.find(obj => obj.props.name === 'emailcheck')
if (!('field' in actionNode.ActionValue) && !('message' in actionNode.ActionValue) && !!!emailAction) {
return result
}
console.log(actionNode);
this.props = actionNode.ActionValue as IMailCheckProps;
//条件式の計算結果を取得
const record = event.record;
const value = record[this.props.field.code].value;
if (emailAction?.props.modelValue === '厳格') {
if (!/^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/.test(value)) {
console.log('厳格');
record[this.props.field.code].error = this.props.message;
}
} else if (emailAction?.props.modelValue === 'ゆるめ') {
if (!/^[^@]+@[^@]+$/.test(value)) {
console.log('ゆるめ');
record[this.props.field.code].error = this.props.message;
}
} else {
result = {
canNext: true,
result: true
}
}
console.log(record[this.props.field.code]);
return result;
} catch (error) {
event.error = error;
console.error(error);
result.canNext = false;
return result;
}
};
register(): void {
actionAddins[this.name] = this;
}
}
new MailCheckAction();

View File

@@ -1,72 +0,0 @@
import { actionAddins } from ".";
import { IAction,IActionResult, IActionNode, IActionProperty, IField} from "../types/ActionTypes";
/**
* アクションの属性定義
*/
interface IRegularCheckProps{
field:IField;//チェックするフィールドの対象
message:string;//エラーメッセージ
regExpression:string;//正規表現式
}
/**
* 正規表現チェックアクション
*/
export class RegularCheckAction implements IAction{
name: string;
actionProps: IActionProperty[];
props:IRegularCheckProps;
constructor(){
this.name="正規表現チェック";
this.actionProps=[];
this.props={
field:{code:''},
message:'',
regExpression:''
}
//アクションを登録する
this.register();
}
/**
* アクションの実行を呼び出す
* @param actionNode
* @param event
* @returns
*/
async process(actionNode:IActionNode,event:any):Promise<IActionResult> {
let result={
canNext:true,
result:false
};
try{
//属性設定を取得する
this.actionProps=actionNode.actionProps;
if (!('field' in actionNode.ActionValue) && !('message' in actionNode.ActionValue) && !('regExpression'in actionNode.ActionValue)) {
return result
}
this.props = actionNode.ActionValue as IRegularCheckProps;
//条件式の計算結果を取得
const record = event.record;
const value = record[this.props.field.code].value;
const regex = new RegExp(this.props.regExpression);
if(!regex.test(value)){
record[this.props.field.code].error=this.props.message;
}else{
result= {
canNext:true,
result:true
}
}
return result;
}catch(error){
event.error=error;
console.error(error);
result.canNext=false;
return result;
}
};
register(): void {
actionAddins[this.name]=this;
}
}
new RegularCheckAction();

View File

@@ -6,9 +6,8 @@ import '../actions/field-shown';
import '../actions/error-show';
import '../actions/button-add';
import '../actions/condition-action';
import '../actions/regular-check';
import '../actions/mail-check';
import '../actions/counter-check';
import '../actions/data-processing';
import '../actions/data-mapping';
import { ActionFlow,IActionFlow, IActionResult,IContext } from "./ActionTypes";
export class ActionProcess{

View File

@@ -1,16 +1,32 @@
// vite.config.js
import { defineConfig } from 'vite'
const sourcemap = process.env.SOURCE_MAP==='true';
import { defineConfig, loadEnv } from "vite";
import checker from "vite-plugin-checker";
import { libInjectCss } from 'vite-plugin-lib-inject-css';
export default defineConfig({
build: {
rollupOptions: {
input: 'src/index.ts', // entry file
output:{
entryFileNames:'alc_runtime.js',
// assetFileNames:'alc_kintone_style.css'
}
export default ({ mode }) => {
process.env = { ...process.env, ...loadEnv(mode, process.cwd()) };
return defineConfig({
plugins: [
checker({
typescript: true,
}),
libInjectCss(),
],
build: {
cssCodeSplit: false,
rollupOptions: {
input: "src/index.ts", // entry file
output: {
entryFileNames: "alc_runtime.js",
// assetFileNames:'alc_kintone_style.css'
},
},
sourcemap: process.env.VITE_SOURCE_MAP,
},
sourcemap:sourcemap
}
})
server: {
port: process.env.VITE_PORT,
// open: "/dist/alc_runtime.js",
},
});
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1