Compare commits

...

197 Commits

Author SHA1 Message Date
xiaozhe.ma
f70c27d814 confilct解決 2024-07-12 19:37:45 +09:00
xiaozhe.ma
432e52d322 Merge branch 'dev' of https://dev.azure.com/rj-se-service-design/App%20Builder%20for%20kintone/_git/App%20Builder%20for%20kintone into dev 2024-07-12 19:20:59 +09:00
xiaozhe.ma
f3893c2500 Conflict解決 2024-07-12 19:17:06 +09:00
xiaozhe.ma
e726843189 データ追加&更新処理アクション修正完了 2024-07-12 19:05:15 +09:00
tenraku ou
183abeba41 Merged PR 44: email-check-Fix Fixed505&507
email-check-Fix Fixed505&507
メールアドレスチェック厳格の修正

Related work items: #505, #507
2024-07-12 05:28:50 +00:00
Shohtetsu Ma
70d2513cd7 Merged PR 45: 値変更イベント非同期処理対応
原因:
ChangeイベントはPromiseの返し値を待たせないので、アクション処理途中event完了してしまうことがあります
対策:
値変更イベントは処理完了後、kintone.app.recordをリセットするように対応します

Related work items: #508
2024-07-12 05:28:28 +00:00
xiaozhe.ma
0fda3d143c 条件式の障害対応(511,512,513) 2024-07-12 09:50:28 +09:00
xiaozhe.ma
a85a3683f2 Merge branch 'feature/data-update' into dev 2024-07-11 23:22:50 +09:00
xiaozhe.ma
14287b6948 data-mappingはdata-updateへ変更 2024-07-11 23:20:33 +09:00
Mouriya
0443257f86 コードのコメントとその他のコンテンツの追加 2024-07-11 21:17:26 +09:00
Mouriya
18b97c249a bug修复 2024-07-11 20:34:32 +09:00
Mouriya
4ac4c9e9f4 「kintone lookup」と組み合わせるとロックされたフィールドを除外します 2024-07-11 20:23:48 +09:00
Mouriya
24a70aed2e 「DataMapping」コンポーネントとプラグイン 新機能「更新対象」 2024-07-11 18:57:30 +09:00
xiaozhe.ma
79e38ba6dd 値変更イベント非同期処理対応 2024-07-11 15:25:10 +09:00
wtl
303a3ffc23 email-check-Fix Fixed505&507 2024-07-11 14:10:16 +09:00
Mouriya
af86edd3e2 損傷した部品 2024-07-11 10:59:36 +09:00
Shohtetsu Ma
c87cff4181 Merged PR 43: データ集計計算アクション
以下の変更があります。
- データ集計アクション
- データ集計関連の共通UIの追加
- 条件式エディタの変更
2024-07-11 00:44:23 +00:00
xiaozhe.ma
05db5a0522 feat:データ集計処理実装完了 2024-07-10 17:26:47 +09:00
xiaozhe.ma
bac7020c15 feat:データ集計処理実装完了 2024-07-10 17:21:07 +09:00
Shohtetsu Ma
c1d33e3ff0 Merged PR 42: feat:lookup同期アクション
feat:lookup同期アクション
以下の変更があります。
1.ルックアップ同期アクション追加
2.BootStrapをPluginに導入
3.フローエディタの共通コンボの改修(Lookup対応)
4.Backendのjs/cssファイルデプロイの改修

Related work items: #241
2024-07-05 08:37:54 +00:00
832d46d360 feat:lookup同期アクション 2024-07-05 17:20:51 +09:00
Yu Wang
c8f9cbda9a Merged PR 41: feat:#262ログインユーザー取得コンポーネントの追加
feat:#262ログインユーザー取得コンポーネントの追加

Related work items: #262
2024-07-05 00:11:36 +00:00
王玉
c1c265c73e feat:#262ログインユーザー取得コンポーネントの追加 2024-07-04 19:48:37 +09:00
e4800d2937 bugfix:Jsファイルでデプロイ時の不具合修正 2024-06-28 02:42:30 +09:00
550e59b4db ZCC対応の改修 2024-06-28 01:09:24 +09:00
Kanaru Tsuda
26a685b872 Merged PR 39: feat 236(全角チェック),284(半角チェック)
コンポーネントの改修

Related work items: #236, #284
2024-06-13 06:32:02 +00:00
Shohtetsu Ma
8514adf15e Merged PR 40: 変数定義の型はstring->IVarNameへ変更
変数定義の型を変更しました。
今後変数はオブジェクト型を対応可能のため、インターフェースを変更しました
下記のアクションを対応しました。
・auto-numbering.ts
・datetime-getter.ts
・value-getter.ts
・condition-action.ts
2024-06-13 06:31:23 +00:00
kanarutsuda
5f2059fd6a feat 236,284 全半角の改修コミット 2024-06-13 15:20:04 +09:00
504a76b4ac feat:変数定義類型の変更対応 2024-06-13 15:16:44 +09:00
kanarutsuda
80694ee49c feat 236,284 全半角のチェック コミット 2024-06-13 15:12:30 +09:00
kanarutsuda
493b9ca0e9 Merge branch 'dev' into feature/validation-half-width 2024-06-13 15:04:20 +09:00
kanarutsuda
55bbf50656 feat 236,284
全半角の改修コミット
2024-06-13 14:51:28 +09:00
4b27504b99 bugfix:フィールド表示障害修正 2024-06-13 13:53:43 +09:00
kanarutsuda
1d248bde43 半角チェック文字コード修正変更 2024-06-12 17:34:13 +09:00
Kanaru Tsuda
5cad10575f Merged PR 38: 半角チェック、全角チェックファイル名変更
それぞれfull widthcheck,half widthcheckをfull-widthcheck,half-widthcheckに変更しました

Related work items: #380
2024-06-12 07:39:58 +00:00
kanarutsuda
3b56c78bf1 ファイル名変更(削除) 2024-06-12 16:37:14 +09:00
kanarutsuda
6ab668f86a ファイル名の変更 2024-06-12 16:13:32 +09:00
kanarutsuda
7b1daaab33 ファイル名の変更 2024-06-12 15:55:38 +09:00
kanarutsuda
ef47912c37 feat: #380 半角チェック
指定したフィールドが半角かどうか判定する(全角が1文字でも含まれていたらエラー表示)
2024-06-12 15:25:50 +09:00
Shohtetsu Ma
140c48bcb7 Merged PR 36: 属性UIの新機能追加
1.フィールド選択UIのフィールド種別指定追加
2.スペースにボタン配置可能にする
3.選択肢UIの複数選択可能にする
4.スペース表示するため、バックエンドの改修
5.ボタンクリックイベントと項目値変更イベントのノード削除機能追加

Related work items: #241, #316, #344
2024-06-11 10:32:54 +00:00
ba0b96146e merge:DEVブランチとマージする 2024-06-11 19:22:05 +09:00
53aa5dff88 env:環境ファイル戻す 2024-06-11 18:21:18 +09:00
e52b02ec7f bugfix:アプリフィールド選択のフィールド種別指定修正 2024-06-11 18:10:04 +09:00
47dbaaf87d 環境変数戻す 2024-06-11 17:16:51 +09:00
478c751ea7 ボタン配置改修 2024-06-11 15:14:31 +09:00
4ee72a8a75 ボタン配置改修 2024-06-11 15:11:24 +09:00
Yukina Mori
e2db112080 Merged PR 33: fix: #234 フィールドに値を挿入するコンポーネントのinsert-value.tsの修正
fix: #234 フィールドに値を挿入するコンポーネントのinsert-value.tsの修正

コンポーネントの動作確認が取れていなかったため、insert-value.tsを修正いたしました。

Related work items: #234
2024-06-11 05:05:41 +00:00
Moriyukina2
3c0d572a0e fix: #234 フィールドに値を挿入するコンポーネントのinsert-value.tsのコメントの修正 2024-06-11 13:58:19 +09:00
Moriyukina2
c225ddd39d fix: #234 フィールドに値を挿入するコンポーネントのinsert-value.tsの修正
コンポーネントの動作確認が取れていなかったため、insert-value.tsを修正いたしました。
2024-06-11 13:19:17 +09:00
0e9b0ea693 feat:タブ対応するためにボタン配置対応 2024-06-11 09:57:52 +09:00
36f225a5b6 Merge remote-tracking branch 'origin/feature-delete-event' into feature/button-on-space 2024-06-11 02:24:10 +09:00
9496128e02 Merge branch 'feature-data-processing' into feature/button-on-space 2024-06-10 11:41:52 +09:00
612962cc83 merge with dev 2024-06-10 11:33:42 +09:00
52514b7197 feat:ボタンをスペースに配置 2024-06-10 11:24:10 +09:00
Kanaru Tsuda
234e55bc01 Merged PR 32: 全角チェック
指定したフィールドが全角かどうかをチェックし、全角でなければエラーを出す。
2024-06-05 06:05:07 +00:00
kanarutsuda
f4a1bc3e58 Merge remote-tracking branch 'origin/dev' into feature/full-width-check 2024-06-05 13:26:29 +09:00
Kanaru Tsuda
192174b2ca Deleted fullwidth-check.ts 2024-06-04 05:45:02 +00:00
kanarutsuda
544370688e 全角チェックのアップデート 2024-06-04 14:35:11 +09:00
Yu Wang
6a6e772e32 Merged PR 29: feat:#239文字結合コンポーネントの追加
文字結合コンポーネントの追加

Related work items: #239
2024-06-03 09:48:31 +00:00
tenraku ou
f4500a09bc Merged PR 28: 追加 フィールドの値を取得する
追加 フィールドの値を取得する

Related work items: #287
2024-06-03 09:46:32 +00:00
takuto
e7c3d3c8ad merge:action-processの整備 2024-06-03 18:30:30 +09:00
王玉
91bd72f7e0 feat:#239文字結合コンポーネントの追加,action-process onflict改修 2024-06-03 17:59:34 +09:00
王玉
2e69dc4dcf feat:#239文字結合コンポーネントの追加 2024-06-03 17:39:54 +09:00
tenraku ou
535049a188 Updated action-process.ts 2024-06-03 07:41:33 +00:00
tenraku ou
5bde55e5fc Updated value-getter.ts 2024-06-03 07:40:41 +00:00
tenraku ou
c1cad3d7a9 Renamed get-value.ts to value-getter.ts 2024-06-03 07:40:09 +00:00
tenraku ou
c378bfe20c Deleted quasar-project 2024-06-03 07:26:53 +00:00
Yukina Mori
0e0d028c24 Merged PR 26: 修正版 feat: #234 フィールドに値を挿入するコンポーネントの追加
feat: #234 フィールドに値を挿入するコンポーネントの追加

フィールドに値を挿入するコンポーネントの新規追加です。

Related work items: #234
2024-06-03 04:57:23 +00:00
Moriyukina2
3b6eed32ec feat: #234 フィールドに値を挿入するコンポーネントの追加
フィールドに値を挿入するコンポーネントの新規追加です。
2024-06-03 13:45:41 +09:00
Takuto Yoshida(タクト)
f62a7c3389 Merged PR 24: feat:現在時刻の取得
feat:現在時刻の取得

Related work items: #237
2024-06-03 03:35:41 +00:00
takuto
a161d8e2c8 feat:現在時刻の取得 2024-06-03 03:35:14 +09:00
wtl
b97888fca9 文字数チェック属性名変更 2024-05-30 15:28:55 +09:00
wtl
371ec3a133 メールチェックインターフェース更新 2024-05-30 15:14:54 +09:00
tenraku ou
711b9afaea Merged PR 23: メールアドレスチェック 文字数チェック 正規表現チェック追加
1.メールアドレスチェック email-check addin
2.文字数チェック counter-check addin
3.正規表現チェック追加 regular-check addin

Related work items: #247, #248, #250
2024-05-30 04:55:09 +00:00
wtl
00227ca713 update console delete 2024-05-29 10:14:52 +09:00
wtl
842edd6f1f update console.log delete 2024-05-29 10:05:47 +09:00
wtl
cd0c3197fa add get-value 2024-05-29 09:53:38 +09:00
wtl
7f35a91765 scriptsファイル削除環境変更 2024-05-28 10:20:22 +09:00
c44b42f498 不要ファイル削除 2024-05-28 09:44:36 +09:00
cff6ee5478 feat:メールアドレスチェック 2024-05-27 21:49:52 +09:00
wtl
40b21604f9 環境変更 2024-05-27 18:04:47 +09:00
wtl
98fcd2eb47 文字数チェック追加 2024-05-27 17:24:03 +09:00
Mouriya
c3b560dbc9 条件付きコンポーネントは'source'でappidを受け取ることができる。 2024-05-25 04:15:09 +09:00
53aadfcaaa feat:データ集計処理作成 2024-05-24 09:20:19 +09:00
wtl
dda9b7adad addNewEmailCheck 2024-05-21 16:12:40 +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
kanarutsuda
22e9094d4c feat全角チェックのソースマージ 2024-05-15 16:19:47 +09:00
kanarutsuda
a13721f63e feat:全角チェックアクション追加 2024-05-15 16:06:42 +09:00
kanarutsuda
05b9a0ce1b feat:全角入力チェックアクション追加 2024-05-15 12:14:28 +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
wtl
5663c313ea Merge branch 'dev' of https://dev.azure.com/alicorn-dev/KintoneAppBuilder/_git/KintoneAppBuilder into tenraku 2024-05-10 17:00:19 +09:00
wtl
7da9b81319 fork 2024-05-10 16:58:06 +09:00
c426bbf793 feat: numInput属性UI追加
inputText,numInputにrules設定追加、入力ルール設定可能
2024-05-09 10:49:05 +09:00
329debaab8 Merge branch 'mvp_step2_dev' into feature-colorpicker 2024-05-07 11:56:20 +09:00
Mouriya
994a0174f5 カラーピッカーと数字入力ボックスの追加 2024-05-06 20:58:06 +09:00
2846297112 アプリからフィールド選択の複数選択・単一選択対応 2024-04-30 12:54:20 +09:00
5cf60ddfdc Merge branch 'mvp_step2_dev' into feature-appfieldselect 2024-04-30 12:09:38 +09:00
0de33f04bc アプリからフィールド選択UI追加 2024-04-25 09:46:34 +09:00
472353632c 長い説明文追加 2024-04-22 22:45:03 +09:00
Mouriya
1a48fb5b20 update 2024-04-22 22:05:39 +09:00
99d3a01991 条件式のバッグ修正 2024-04-18 18:55:59 +09:00
wtl
da24972482 正規表現アクション実装 2024-04-15 18:08:22 +09:00
ecb90e7120 ダイアログに検索追加 2024-04-15 16:54:33 +09:00
wtl
784cb7a473 newActionRegularCheck 2024-03-29 17:04:39 +09:00
5349c46225 不要ファイル削除 2024-03-29 10:52:29 +09:00
3be4402239 アクションフローBugFix 2024-03-27 02:01:12 +09:00
4c482ea289 条件アクションの障害修正 2024-03-26 17:08:26 +09:00
44a73478a7 フローエディタの左パネル表示・非表示機能追加 2024-03-12 18:04:50 +09:00
bceac2f172 config変更 2024-03-04 12:53:23 +09:00
98842db343 Merge branch 'mvp_step2_dev' of https://dev.azure.com/alicorn-dev/KintoneAppBuilder/_git/KintoneAppBuilder into mvp_step2_dev 2024-03-01 22:49:41 +09:00
03904a4e35 ボタンクリックイベント対応 2024-03-01 22:47:37 +09:00
09b3c8df47 eventaction blacklist 2024-02-28 18:53:40 +09:00
26761f6d39 add eventgroup->event 2024-02-28 18:32:57 +09:00
72608a8ffd アクションフローの不具合改修 2024-02-26 12:20:31 +09:00
d1ec123c8b ActionFlow障害修正 2024-02-25 02:52:06 +09:00
4102ff5522 変数設定を追加 2024-02-21 16:28:43 +09:00
08e857884b Merge branch 'mvp_step2_dev' of https://dev.azure.com/alicorn-dev/KintoneAppBuilder/_git/KintoneAppBuilder into mvp_step2_dev 2024-02-17 23:35:20 +09:00
c0db2d230b 条件式比較バグ修正 2024-02-16 14:09:43 +09:00
2b9b772b39 設計書取込機能追加 2024-02-13 14:34:53 +09:00
c46e8a7047 Merge branch 'mvp_step2_back' into mvp_step2_dev 2024-02-13 14:25:49 +09:00
6e6350d6ce 設計書取込機能追加 2024-02-13 14:25:09 +09:00
1e7d553bd6 kintone excel new format 2024-02-06 17:37:55 +09:00
35ae2539cb kintone excel new format 2024-02-06 17:37:29 +09:00
wtl
a614d754f4 ErrorShowNewVer 2024-02-06 15:33:47 +09:00
46a6ba534e Merge branch 'mvp_step2_dev' of https://dev.azure.com/alicorn-dev/KintoneAppBuilder/_git/KintoneAppBuilder into mvp_step2_dev 2024-02-03 11:56:13 +09:00
8fecde4c42 left menu 修正 2024-02-03 11:55:44 +09:00
wtl
3e73799532 New Plagin Error Show 2024-02-02 12:52:00 +09:00
3159366560 Actionアドインの開発手順作成 2024-02-01 10:58:39 +09:00
5176cff2bd action開発手順作成 2024-01-31 15:34:29 +09:00
978aa723ae action開発手順作成 2024-01-31 15:33:18 +09:00
926c338f73 Merge branch 'mvp_step2_dev' of https://dev.azure.com/alicorn-dev/KintoneAppBuilder/_git/KintoneAppBuilder into mvp_step2_dev 2024-01-31 05:37:04 +09:00
6de60c82ba 条件エディタ実装 2024-01-31 05:22:09 +09:00
6ed17a50e5 token 15m->48h 2024-01-30 12:36:33 +09:00
5cd6d02f6e 条件エディタ追加 2024-01-22 10:52:55 +09:00
276e5e9122 condition test 2023-12-25 17:11:11 +09:00
6e75a2a524 condtion tree 2023-12-25 17:07:40 +09:00
ea6e603036 update APIException 2023-11-22 14:35:37 +09:00
edad30e264 exception の変更 2023-11-21 21:50:50 +09:00
58616100f4 Merge branch 'mvp_step2_dev' of https://dev.azure.com/alicorn-dev/KintoneAppBuilder/_git/KintoneAppBuilder into mvp_step2_dev 2023-11-21 21:22:47 +09:00
359558bad3 add dev build mode 2023-11-21 21:22:30 +09:00
6a6554ed1f add APIException 2023-11-21 21:20:02 +09:00
e20625abdb domain切替バグ修正 2023-11-20 01:50:02 +09:00
f83dd693d5 log bugfix 2023-11-19 13:37:55 +09:00
a464297511 add api.log&errorlog->db 2023-11-19 13:24:29 +09:00
991c8e8083 add api.log&errorlog->db 2023-11-19 13:24:08 +09:00
9ea183ff2d 処理中表示追加 2023-11-15 03:13:24 +09:00
34d368b730 ダイアログ表示時snipper追加 2023-11-15 00:18:06 +09:00
17760a6926 FlowChart のレイアウト修正 2023-11-14 09:11:46 +09:00
8b9cfa34c7 Merge branch 'mvp_step2_dev' of https://dev.azure.com/alicorn-dev/KintoneAppBuilder/_git/KintoneAppBuilder into mvp_step2_dev 2023-11-13 22:04:27 +09:00
5fb8fe53bb backend bug fix 2023-11-13 22:03:45 +09:00
5cb9375db3 add domainid->flow 2023-11-13 18:02:19 +09:00
55181f2c57 ユーザー追加API Bug fix 2023-11-10 02:34:50 +09:00
4577df371a Token無効の際再ログイン対応 2023-11-09 13:47:21 +09:00
0f154832a5 前端APIのURL参数化対応およびバックエンドのBugFix 2023-11-08 15:44:42 +09:00
5951fcc108 depoly bug fix 2023-11-04 22:54:21 +09:00
7966217ac2 add js dev model 2023-11-04 22:40:40 +09:00
64851bd51d added deploy DEV mode 2023-11-04 22:35:23 +09:00
10e584d2ac bugfix 2023-11-04 22:22:26 +09:00
b97a728624 kintone ENV bugfix 2023-11-04 22:16:23 +09:00
5a875e6853 domain bug fix 2023-11-04 22:11:32 +09:00
57a4823f61 Merge branch 'mvp_feature3_dev' of https://dev.azure.com/alicorn-dev/KintoneAppBuilder/_git/KintoneAppBuilder into mvp_feature3_dev 2023-11-04 17:17:08 +09:00
761eb4c13e API token 2023-11-04 17:16:57 +09:00
354fc6868d remove id from localStorage 2023-11-04 17:15:17 +09:00
2538e4526f first&last name 2023-11-04 17:13:44 +09:00
26890f5b35 Merge branch 'mvp_feature3_dev' of https://dev.azure.com/alicorn-dev/KintoneAppBuilder/_git/KintoneAppBuilder into mvp_feature3_dev 2023-11-04 15:15:44 +09:00
086b5e2621 Azure env 2023-11-04 15:15:35 +09:00
617b060869 login user 2023-11-04 15:12:27 +09:00
a782e92bd6 自動採番障害対応 2023-11-01 22:59:23 +09:00
f60f97380f domain 2023-11-01 22:56:47 +09:00
cfc416fd14 自動採番アクション追加・ドメイン追加 2023-11-01 10:47:24 +09:00
df593d2ffe modified ER 2023-10-31 03:42:25 +09:00
9cd4c8a5ab event&eventaction 2023-10-29 17:16:50 +09:00
maxiaozhe
ead6658455 floweditor 修正 2023-10-26 09:15:34 +09:00
f6d677b51f Kintone plugin 実装 2023-10-24 09:08:45 +09:00
25f05ab018 Kintoneがわファイルアップロード 2023-10-20 12:28:07 +09:00
178cf33949 deploy機能実装 2023-10-16 17:13:14 +09:00
b54c0f8022 Merge branch 'mvp_step2_dev' of https://dev.azure.com/alicorn-dev/KintoneAppBuilder/_git/KintoneAppBuilder into mvp_step2_dev 2023-10-16 14:02:16 +09:00
c5cc3c1a24 UI修正 2023-10-16 14:02:01 +09:00
0b414fbfbe フロー保存の実装 2023-10-16 13:38:51 +09:00
981d7a5062 bugfix 2023-10-15 23:35:06 +09:00
33fc0b74ef createjstokintone add 2023-10-15 23:34:12 +09:00
286acc4584 upload kintone jscss file 2023-10-15 19:30:35 +09:00
cdfb1d4310 Merge branch 'maxz-real-impl' into mvp_step2_dev 2023-10-15 18:17:32 +09:00
6844652b5d ER図変更 2023-10-12 16:29:51 +09:00
76457b6667 UserDomain Add 2023-10-09 15:55:58 +09:00
e1f2afa942 sqlserver->postgresql 2023-09-30 15:16:05 +09:00
8d5dff60f1 Merge branch 'maxz-real-impl' into mvp_step2_dev 2023-09-30 13:33:39 +09:00
6aa057e590 flow api add 2023-09-21 07:22:12 +00:00
2721cd60d1 action bugfix 2023-09-18 02:54:53 +00:00
1f8d079d4d action bugfix 2023-09-18 02:54:35 +00:00
3ae685a0e2 action add subtitle 2023-09-17 08:54:54 +00:00
c1e50736e8 action table add 2023-09-16 01:52:15 +00:00
140 changed files with 16047 additions and 3115 deletions

2
backend/.gitignore vendored
View File

@@ -56,6 +56,7 @@ coverage.xml
# Django stuff:
*.log
*.log.*
local_settings.py
db.sqlite3
db.sqlite3-journal
@@ -126,3 +127,4 @@ cython_debug/
.vscode/
*.lock
Temp/

View File

@@ -1,5 +1,5 @@
FROM python:3.8
FROM python:3.11.4
RUN mkdir /app
WORKDIR /app

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -29,18 +29,19 @@ async def login(
else:
permissions = "user"
access_token = security.create_access_token(
data={"sub": user.email, "permissions": permissions},
data={"sub": user.id, "permissions": permissions},
expires_delta=access_token_expires,
)
return {"access_token": access_token, "token_type": "bearer"}
return {"access_token": access_token, "token_type": "bearer","user_name":user.first_name + " " + user.last_name}
@r.post("/signup")
async def signup(
firstname:str, lastname:str,
db=Depends(get_db), form_data: OAuth2PasswordRequestForm = Depends()
):
user = sign_up_new_user(db, form_data.username, form_data.password)
user = sign_up_new_user(db, form_data.username, form_data.password,firstname,lastname)
if not user:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
@@ -56,8 +57,8 @@ async def signup(
else:
permissions = "user"
access_token = security.create_access_token(
data={"sub": user.email, "permissions": permissions},
data={"sub": user.id, "permissions": permissions},
expires_delta=access_token_expires,
)
return {"access_token": access_token, "token_type": "bearer"}
return {"access_token": access_token, "token_type": "bearer","user_name":user.first_name + " " + user.last_name}

View File

@@ -5,33 +5,150 @@ import pandas as pd
import json
import httpx
import deepdiff
import app.core.config as c
import app.core.config as config
import os
from pathlib import Path
from app.db.session import SessionLocal
from app.db.crud import get_flows_by_app,get_activedomain,get_kintoneformat
from app.core.auth import get_current_active_user,get_current_user
from app.core.apiexception import APIException
kinton_router = r = APIRouter()
def getfieldsfromexcel(df):
def getkintoneenv(user = Depends(get_current_user)):
db = SessionLocal()
domain = get_activedomain(db, user.id)
db.close()
kintoneevn = config.KINTONE_ENV(domain)
return kintoneevn
def getkintoneformat():
db = SessionLocal()
formats = get_kintoneformat(db)
db.close()
return formats
def createkintonefields(property,value,trueformat):
p = []
if(property=="options"):
o=[]
for v in value.split(','):
o.append(f"\"{v.split('|')[0]}\":{{\"label\":\"{v.split('|')[0]}\",\"index\":\"{v.split('|')[1]}\"}}")
p.append(f"\"options\":{{{','.join(o)}}}")
elif(property =="expression"):
p.append(f"\"hideExpression\":true")
p.append(f"\"expression\":\"{value.split(':')[1]}\"")
elif(property =="required" or property =="unique" or property =="defaultNowValue" or property =="hideExpression" or property =="digit"):
if str(value) == trueformat:
p.append(f"\"{property}\":true")
else:
p.append(f"\"{property}\":false")
elif(property =="protocol"):
if(value == "メールアドレス"):
p.append("\"protocol\":\"MAIL\"")
elif(value == "Webサイト"):
p.append("\"protocol\":\"WEB\"")
elif(value == "電話番号"):
p.append("\"protocol\":\"CALL\"")
else:
p.append(f"\"{property}\":\"{value}\"")
return p
def getfieldsfromexcel(df,mapping):
startrow = mapping.startrow
startcolumn = mapping.startcolumn
typecolumn = mapping.typecolumn
codecolumn = mapping.codecolumn
property = mapping.field.split(",")
trueformat = mapping.trueformat
appname = df.iloc[0,2]
col=[]
for row in range(5,len(df)):
if pd.isna(df.iloc[row,1]):
for row in range(startrow,len(df)):
if pd.isna(df.iloc[row,startcolumn]):
break
if not df.iloc[row,3] in c.KINTONE_FIELD_TYPE:
if not df.iloc[row,typecolumn] in config.KINTONE_FIELD_TYPE:
continue
p=[]
for column in range(1,7):
for column in range(startcolumn,startcolumn + len(property)):
if(not pd.isna(df.iloc[row,column])):
if(property[column-1]=="options"):
o=[]
for v in df.iloc[row,column].split(','):
o.append(f"\"{v.split('|')[0]}\":{{\"label\":\"{v.split('|')[0]}\",\"index\":\"{v.split('|')[1]}\"}}")
p.append(f"\"{property[column-1]}\":{{{','.join(o)}}}")
elif(property[column-1]=="required"):
p.append(f"\"{property[column-1]}\":{df.iloc[row,column]}")
propertyname =property[column-1]
if(propertyname.find("[") == 0):
continue
elif (propertyname =="remark"):
if (df.iloc[row,column].find("|") !=-1):
propertyname = "options"
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
if (df.iloc[row,column] == "メールアドレス" or df.iloc[row,column] == "Webサイト" or df.iloc[row,column] == "電話番号"):
propertyname = "protocol"
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
if (df.iloc[row,column].find("桁区切り") !=-1):
propertyname = "digit"
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
if (df.iloc[row,column].find("前単位") !=-1):
propertyname = "unitPosition"
p = p + createkintonefields(propertyname, "BEFORE",trueformat)
if (df.iloc[row,column].find("後単位") !=-1):
propertyname = "unitPosition"
p = p + createkintonefields(propertyname, "AFTER",trueformat)
if (df.iloc[row,column].find("単位「") !=-1):
propertyname = "unit"
ids = df.iloc[row,column].index("単位「")
ide = df.iloc[row,column].index("")
unit = df.iloc[row,column][ids+3:ide]
p = p + createkintonefields(propertyname, unit,trueformat)
else:
continue
elif(propertyname =="mixValue"):
if(df.iloc[row,column].find("レコード登録時の日") != -1):
propertyname = "defaultNowValue"
df.iloc[row,column] = trueformat
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
elif(df.iloc[row,column].find("計:") != -1):
propertyname = "expression"
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
elif(df.iloc[row,column] !=""):
propertyname = "defaultValue"
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
else:
continue
elif(propertyname=="max" or propertyname == "min"):
if(df.iloc[row,typecolumn] == "NUMBER"):
propertyname = property[column-1] + "Value"
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
else:
propertyname = property[column-1] + "Length"
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
else:
p.append(f"\"{property[column-1]}\":\"{df.iloc[row,column]}\"")
col.append(f"\"{df.iloc[row,2]}\":{{{','.join(p)}}}")
fields = ",".join(col).replace("False","false").replace("True","true")
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
# if(propertyname=="options"):
# o=[]
# for v in df.iloc[row,column].split(','):
# o.append(f"\"{v.split('|')[0]}\":{{\"label\":\"{v.split('|')[0]}\",\"index\":\"{v.split('|')[1]}\"}}")
# p.append(f"\"options\":{{{','.join(o)}}}")
# elif(propertyname=="expression"):
# p.append(f"\"hideExpression\":true")
# p.append(f"\"expression\":{df.iloc[row,column].split(':')[1]}")
# elif(propertyname=="required" or propertyname =="unique" or propertyname=="defaultNowValue" or propertyname=="hideExpression" or propertyname=="digit"):
# if (df.iloc[row,column] == trueformat):
# p.append(f"\"{propertyname}\":true")
# else:
# p.append(f"\"{propertyname}\":false")
# elif(propertyname =="protocol"):
# if(df.iloc[row,column] == "メールアドレス"):
# p.append("\"protocol\":\"MAIL\"")
# elif(df.iloc[row,column] == "Webサイト"):
# p.append("\"protocol\":\"WEB\"")
# elif(df.iloc[row,column] == "電話番号"):
# p.append("\"protocol\":\"CALL\"")
# else:
# p.append(f"\"{propertyname}\":\"{df.iloc[row,column]}\"")
col.append(f"\"{df.iloc[row,codecolumn]}\":{{{','.join(p)}}}")
fields = ",".join(col).replace("\\", "\\\\")
return json.loads(f"{{{fields}}}")
def getsettingfromexcel(df):
@@ -39,10 +156,10 @@ def getsettingfromexcel(df):
des = df.iloc[2,2]
return {"name":appname,"description":des}
def getsettingfromkintone(app:str):
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
def getsettingfromkintone(app:str,c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
params = {"app":app}
url = f"{c.BASE_URL}{c.API_V1_STR}/app/settings.json"
url = f"{c.BASE_URL}{config.API_V1_STR}/app/settings.json"
r = httpx.get(url,headers=headers,params=params)
return r.json()
@@ -54,24 +171,24 @@ def analysesettings(excel,kintone):
updatesettings[key] = excel[key]
return updatesettings
def createkintoneapp(name:str):
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
def createkintoneapp(name:str,c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
data = {"name":name}
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app.json"
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app.json"
r = httpx.post(url,headers=headers,data=json.dumps(data))
return r.json()
def updateappsettingstokintone(app:str,updates:dict):
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/settings.json"
def updateappsettingstokintone(app:str,updates:dict,c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/settings.json"
data = {"app":app}
data.update(updates)
r = httpx.put(url,headers=headers,data=json.dumps(data))
return r.json()
def addfieldstokintone(app:str,fields:dict,revision:str = None):
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/form/fields.json"
def addfieldstokintone(app:str,fields:dict,c:config.KINTONE_ENV,revision:str = None):
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
if revision != None:
data = {"app":app,"revision":revision,"properties":fields}
else:
@@ -79,35 +196,75 @@ def addfieldstokintone(app:str,fields:dict,revision:str = None):
r = httpx.post(url,headers=headers,data=json.dumps(data))
return r.json()
def updatefieldstokintone(app:str,revision:str,fields:dict):
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/form/fields.json"
def updatefieldstokintone(app:str,revision:str,fields:dict,c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
data = {"app":app,"properties":fields}
r = httpx.put(url,headers=headers,data=json.dumps(data))
return r.json()
def deletefieldsfromkintone(app:str,revision:str,fields:dict):
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/form/fields.json"
def deletefieldsfromkintone(app:str,revision:str,fields:dict,c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
params = {"app":app,"revision":revision,"fields":fields}
#r = httpx.delete(url,headers=headers,content=json.dumps(params))
r = httpx.request(method="DELETE",url=url,headers=headers,content=json.dumps(params))
return r.json()
def deoployappfromkintone(app:str,revision:str):
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/deploy.json"
def deoployappfromkintone(app:str,revision:str,c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/deploy.json"
data = {"apps":[{"app":app,"revision":revision}],"revert": False}
r = httpx.post(url,headers=headers,data=json.dumps(data))
return r.json
def getfieldsfromkintone(app):
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
# 既定項目に含めるアプリのフィールドのみ取得する
# スペース、枠線、ラベルを含まない
def getfieldsfromkintone(app:str,c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
params = {"app":app}
url = f"{c.BASE_URL}{c.API_V1_STR}/app/form/fields.json"
url = f"{c.BASE_URL}{config.API_V1_STR}/app/form/fields.json"
r = httpx.get(url,headers=headers,params=params)
return r.json()
# フォームに配置するフィールドのみ取得する
# スペース、枠線、ラベルも含める
def getformfromkintone(app:str,c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
params = {"app":app}
url = f"{c.BASE_URL}{config.API_V1_STR}/form.json"
r = httpx.get(url,headers=headers,params=params)
return r.json()
def merge_kintone_fields(fields_response: dict, form_response: dict) -> dict:
fields_properties = fields_response.get('properties', {})
form_properties = form_response.get('properties', [])
merged_properties = {k: v for k, v in fields_properties.items()}
for index, form_field in enumerate(form_properties):
code = form_field.get('code')
if code:
if code and code not in merged_properties:
merged_properties[code] = form_field
else:
element_id = form_field.get('elementId')
if element_id:
key = element_id
form_field['code']=element_id
form_field['label']=form_field.get('type')
# else:
# key = f"{form_field.get('type')}_{index}"
merged_properties[key] = form_field
merged_response = {
'revision': fields_response.get('revision', ''),
'properties': merged_properties
}
return merged_response
def analysefields(excel,kintone):
updatefields={}
addfields={}
@@ -116,22 +273,22 @@ def analysefields(excel,kintone):
adds = excel.keys() - kintone.keys()
dels = kintone.keys() - excel.keys()
for key in updates:
for p in property:
if excel[key].get(p) != None and kintone[key][p] != excel[key][p]:
for p in config.KINTONE_FIELD_PROPERTY:
if excel[key].get(p) != None and kintone[key].get(p) != None and kintone[key][p] != excel[key][p]:
updatefields[key] = excel[key]
break
for key in adds:
addfields[key] = excel[key]
for key in dels:
if kintone[key]["type"] in c.KINTONE_FIELD_TYPE:
if kintone[key]["type"] in config.KINTONE_FIELD_TYPE:
delfields.append(key)
return {"update":updatefields,"add":addfields,"del":delfields}
def getprocessfromkintone(app:str):
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
def getprocessfromkintone(app:str,c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
params = {"app":app}
url = f"{c.BASE_URL}{c.API_V1_STR}/app/status.json"
url = f"{c.BASE_URL}{config.API_V1_STR}/app/status.json"
r = httpx.get(url,headers=headers,params=params)
return r.json()
@@ -195,27 +352,81 @@ def analysprocess(excel,kintone):
# return True
return diff
def updateprocesstokintone(app:str,process:dict):
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/status.json"
def updateprocesstokintone(app:str,process:dict,c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/status.json"
data = {"app":app,"enable":True}
data.update(process)
r = httpx.put(url,headers=headers,data=json.dumps(data))
return r.json()
def getkintoneusers():
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
def getkintoneusers(c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
url = f"{c.BASE_URL}/v1/users.json"
r = httpx.get(url,headers=headers)
return r.json()
def getkintoneorgs():
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
def getkintoneorgs(c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
params = {"code":c.KINTONE_USER}
url = f"{c.BASE_URL}/v1/user/organizations.json"
r = httpx.get(url,headers=headers,params=params)
return r.json()
def uploadkintonefiles(file,c:config.KINTONE_ENV):
if (file.endswith('alc_runtime.js') and config.DEPLOY_MODE == "DEV"):
return {'fileKey':file}
upload_files = {'file': open(file,'rb')}
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
data ={'name':'file','filename':os.path.basename(file)}
url = f"{c.BASE_URL}/k/v1/file.json"
r = httpx.post(url,headers=headers,data=data,files=upload_files)
return r.json()
def updateappjscss(app,uploads,c:config.KINTONE_ENV):
dsjs = []
dscss = []
for upload in uploads:
for key in upload:
if key.endswith('.js'):
if (key.endswith('alc_runtime.js') and config.DEPLOY_MODE == "DEV"):
dsjs.append({'type':'URL','url':config.DEPLOY_JS_URL})
else:
dsjs.append({'type':'FILE','file':{'fileKey':upload[key]}})
elif key.endswith('.css'):
dscss.append({'type':'FILE','file':{'fileKey':upload[key]}})
ds ={'js':dsjs,'css':dscss}
mb ={'js':[],'css':[]}
data = {'app':app,'scope':'ALL','desktop':ds,'mobile':mb}
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/customize.json"
print(data)
r = httpx.put(url,headers=headers,data=json.dumps(data))
return r.json()
def getTempPath(filename):
scriptdir = Path(__file__).resolve().parent
rootdir = scriptdir.parent.parent.parent.parent
fpath = os.path.join(rootdir,"Temp",filename)
return fpath
def createappjs(domainid,app):
db = SessionLocal()
flows = get_flows_by_app(db,domainid,app)
db.close()
content={}
for flow in flows:
content[flow.eventid] = {'flowid':flow.flowid,'name':flow.name,'content':flow.content}
js = 'const alcflow=' + json.dumps(content)
# scriptdir = Path(__file__).resolve().parent
# rootdir = scriptdir.parent.parent.parent.parent
# fpath = os.path.join(rootdir,"Temp",f"alc_setting_{app}.js")
fpath = getTempPath(f"alc_setting_{app}.js")
print(fpath)
with open(fpath,'w') as file:
file.write(js)
return fpath
@r.post("/test",)
async def test(file:UploadFile= File(...),app:str=None):
if file.filename.endswith('.xlsx'):
@@ -233,15 +444,26 @@ async def test(file:UploadFile= File(...),app:str=None):
# kintone = getfieldsfromkintone(app)
# fields = analysefields(excel,kintone["properties"])
except Exception as e:
raise HTTPException(status_code=400, detail=f"Error occurred while parsing file {file.filename}: {str(e)}")
raise HTTPException(status_code=400, detail=f"Error occurred while parsing file {file.filename}")
else:
raise HTTPException(status_code=400, detail=f"File {file.filename} is not an Excel file")
return test
@r.post("/upload",)
async def upload(files:t.List[UploadFile] = File(...)):
@r.post("/download",)
async def download(request:Request,key,c:config.KINTONE_ENV=Depends(getkintoneenv)):
try:
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
params = {"fileKey":key}
url = f"{c.BASE_URL}/k/v1/file.json"
r = httpx.get(url,headers=headers,params=params)
return r.json()
except Exception as e:
raise APIException('kintone:upload',request.url._url,f"Error occurred while download file.json:",e)
@r.post("/upload")
async def upload(request:Request,files:t.List[UploadFile] = File(...)):
dataframes = []
for file in files:
if file.filename.endswith('.xlsx'):
@@ -251,64 +473,112 @@ async def upload(files:t.List[UploadFile] = File(...)):
print(df)
dataframes.append(df)
except Exception as e:
raise HTTPException(status_code=400, detail=f"Error occurred while parsing file {file.filename}: {str(e)}")
raise APIException('kintone:upload',request.url._url,f"Error occurred while uploading file {file.filename}:",e)
else:
raise HTTPException(status_code=400, detail=f"File {file.filename} is not an Excel file")
raise APIException('kintone:upload',request.url._url, f"File {file.filename} is not an Excel file",e)
return {"files": [file.filename for file in files]}
@r.get("/allapps",)
async def allapps():
@r.post("/updatejscss")
async def jscss(request:Request,app:str,files:t.List[UploadFile] = File(...),env = Depends(getkintoneenv)):
try:
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
url = f"{c.BASE_URL}{c.API_V1_STR}/apps.json"
r = httpx.get(url,headers=headers,timeout=httpx.Timeout(10))
return r.json()
jscs=[]
for file in files:
fbytes = file.file.read()
fname = file.filename
fpath = '{}\\{}'.format('Temp',fname)
fout = open(fpath,'wb')
fout.write(fbytes)
fout.close()
upload = uploadkintonefiles(fpath,env)
if upload.get('fileKey') != None:
jscs.append({ file.filename:upload['fileKey']})
appjscs = updateappjscss(app,jscs,env)
if appjscs.get("revision") != None:
deoployappfromkintone(app,appjscs["revision"],env)
return appjscs
except Exception as e:
print(f"異常発生しました。{type(e).__name__},{e}")
raise APIException('kintone:updatejscss',request.url._url, f"Error occurred while update js/css {file.filename} is not an Excel file",e)
@r.get("/app")
async def app(app:str):
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
url = f"{c.BASE_URL}{c.API_V1_STR}/app.json"
params ={"id":app}
r = httpx.get(url,headers=headers,params=params)
return r.json()
async def app(request:Request,app:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
try:
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
url = f"{c.BASE_URL}{config.API_V1_STR}/app.json"
params ={"id":app}
r = httpx.get(url,headers=headers,params=params)
return r.json()
except Exception as e:
raise APIException('kintone:app',request.url._url, f"Error occurred while get app({c.DOMAIN_NAME}->{app}):",e)
@r.get("/allapps")
async def allapps(request:Request,c:config.KINTONE_ENV=Depends(getkintoneenv)):
try:
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
url = f"{c.BASE_URL}{config.API_V1_STR}/apps.json"
r = httpx.get(url,headers=headers)
return r.json()
except Exception as e:
raise APIException('kintone:allapps',request.url._url, f"Error occurred while get allapps({c.DOMAIN_NAME}):",e)
@r.get("/appfields")
async def appfields(app:str):
return getfieldsfromkintone(app)
async def appfields(request:Request,app:str,env = Depends(getkintoneenv)):
try:
return getfieldsfromkintone(app,env)
except Exception as e:
raise APIException('kintone:appfields',request.url._url, f"Error occurred while get app fileds({env.DOMAIN_NAME}->{app}):",e)
@r.get("/allfields")
async def allfields(request:Request,app:str,env = Depends(getkintoneenv)):
try:
field_resp = getfieldsfromkintone(app,env)
form_resp = getformfromkintone(app,env)
return merge_kintone_fields(field_resp,form_resp)
except Exception as e:
raise APIException('kintone:allfields',request.url._url, f"Error occurred while get form fileds({env.DOMAIN_NAME}->{app}):",e)
@r.get("/appprocess")
async def appprocess(app:str):
return getprocessfromkintone(app)
async def appprocess(request:Request,app:str,env = Depends(getkintoneenv)):
try:
return getprocessfromkintone(app,env)
except Exception as e:
raise APIException('kintone:appprocess',request.url._url, f"Error occurred while get app process({env.DOMAIN_NAME}->{app}):",e)
@r.get("/alljscs")
async def alljscs(app:str):
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
url = f"{c.BASE_URL}{c.API_V1_STR}/app/customize.json"
params = {"app":app}
r = httpx.get(url,headers=headers,params=params)
return r.json()
@r.get("/alljscss")
async def alljscs(request:Request,app:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
try:
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
url = f"{c.BASE_URL}{config.API_V1_STR}/app/customize.json"
params = {"app":app}
r = httpx.get(url,headers=headers,params=params)
return r.json()
except Exception as e:
raise APIException('kintone:alljscss',request.url._url, f"Error occurred while get app js/css({c.DOMAIN_NAME}->{app}):",e)
@r.post("/createapp",)
async def createapp(name:str):
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
data = {"name":name}
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app.json"
r = httpx.post(url,headers=headers,data=json.dumps(data))
result = r.json()
if result.get("app") != None:
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/deploy.json"
data = {"apps":[result],"revert": False}
async def createapp(request:Request,name:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
try:
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
data = {"name":name}
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app.json"
r = httpx.post(url,headers=headers,data=json.dumps(data))
return r.json
result = r.json()
if result.get("app") != None:
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/deploy.json"
data = {"apps":[result],"revert": False}
r = httpx.post(url,headers=headers,data=json.dumps(data))
return r.json
except Exception as e:
raise APIException('kintone:createapp',request.url._url, f"Error occurred while create app({c.DOMAIN_NAME}->{name}):",e)
property=["label","code","type","required","defaultValue","options"]
@r.post("/createappfromexcel",)
async def createappfromexcel(files:t.List[UploadFile] = File(...)):
async def createappfromexcel(request:Request,files:t.List[UploadFile] = File(...),format:int = 0,env = Depends(getkintoneenv)):
try:
mapping = getkintoneformat()[format]
except Exception as e:
raise APIException('kintone:createappfromexcel',request.url._url, f"Error occurred while get kintone format:",e)
for file in files:
if file.filename.endswith('.xlsx'):
try:
@@ -318,87 +588,90 @@ async def createappfromexcel(files:t.List[UploadFile] = File(...)):
appname = df.iloc[0,2]
desc = df.iloc[2,2]
result = {"app":0,"revision":0,"msg":""}
fields = getfieldsfromexcel(df)
users = getkintoneusers()
orgs = getkintoneorgs()
fields = getfieldsfromexcel(df,mapping)
users = getkintoneusers(env)
orgs = getkintoneorgs(env)
processes = getprocessfromexcel(df,users["users"], orgs["organizationTitles"])
app = createkintoneapp(appname)
app = createkintoneapp(appname,env)
if app.get("app") != None:
result["app"] = app["app"]
app = updateappsettingstokintone(result["app"],{"description":desc})
app = updateappsettingstokintone(result["app"],{"description":desc},env)
if app.get("revision") != None:
result["revision"] = app["revision"]
app = addfieldstokintone(result["app"],fields)
app = addfieldstokintone(result["app"],fields,env)
if len(processes)> 0:
app = updateprocesstokintone(result["app"],processes)
app = updateprocesstokintone(result["app"],processes,env)
if app.get("revision") != None:
result["revision"] = app["revision"]
deoployappfromkintone(result["app"],result["revision"])
deoployappfromkintone(result["app"],result["revision"],env)
except Exception as e:
raise HTTPException(status_code=400, detail=f"Error occurred while parsing file {file.filename}: {str(e)}")
raise APIException('kintone:createappfromexcel',request.url._url, f"Error occurred while parsing file ({env.DOMAIN_NAME}->{file.filename}):",e)
else:
raise HTTPException(status_code=400, detail=f"File {file.filename} is not an Excel file")
raise APIException('kintone:createappfromexcel',request.url._url, f"File {file.filename} is not an Excel file",e)
return result
@r.post("/updateappfromexcel",)
async def updateappfromexcel(app:str,files:t.List[UploadFile] = File(...)):
@r.post("/updateappfromexcel")
async def updateappfromexcel(request:Request,app:str,files:t.List[UploadFile] = File(...),format:int = 0,env = Depends(getkintoneenv)):
try:
mapping = getkintoneformat()[format]
except Exception as e:
raise APIException('kintone:updateappfromexcel',request.url._url, f"Error occurred while get kintone format:",e)
for file in files:
if file.filename.endswith('.xlsx'):
try:
content = await file.read()
df = pd.read_excel(BytesIO(content))
excel = getsettingfromexcel(df)
kintone= getsettingfromkintone(app)
kintone= getsettingfromkintone(app,env)
settings = analysesettings(excel,kintone)
excel = getfieldsfromexcel(df)
kintone = getfieldsfromkintone(app)
users = getkintoneusers()
orgs = getkintoneorgs()
excel = getfieldsfromexcel(df,mapping)
kintone = getfieldsfromkintone(app,env)
users = getkintoneusers(env)
orgs = getkintoneorgs(env)
exp = getprocessfromexcel(df,users["users"], orgs["organizationTitles"])
#exp = getprocessfromexcel(df)
kinp = getprocessfromkintone(app)
kinp = getprocessfromkintone(app,env)
process = analysprocess(exp,kinp)
revision = kintone["revision"]
fields = analysefields(excel,kintone["properties"])
result = {"app":app,"revision":revision,"msg":"No Update"}
deploy = False
if len(fields["update"]) > 0:
result = updatefieldstokintone(app,revision,fields["update"])
result = updatefieldstokintone(app,revision,fields["update"],env)
revision = result["revision"]
deploy = True
if len(fields["add"]) > 0:
result = addfieldstokintone(app,fields["add"],revision)
result = addfieldstokintone(app,fields["add"],env,revision)
revision = result["revision"]
deploy = True
if len(fields["del"]) > 0:
result = deletefieldsfromkintone(app,revision,fields["del"])
result = deletefieldsfromkintone(app,revision,fields["del"],env)
revision = result["revision"]
deploy = True
if len(settings) > 0:
result = updateappsettingstokintone(app,settings)
result = updateappsettingstokintone(app,settings,env)
revision = result["revision"]
deploy = True
if len(process)>0:
result = updateprocesstokintone(app,exp)
result = updateprocesstokintone(app,exp,env)
revision = result["revision"]
deploy = True
if deploy:
result = deoployappfromkintone(app,revision)
result = deoployappfromkintone(app,revision,env)
except Exception as e:
raise HTTPException(status_code=400, detail=f"Error occurred while parsing file {file.filename}: {str(e)}")
raise APIException('kintone:updateappfromexcel',request.url._url, f"Error occurred while parsing file ({env.DOMAIN_NAME}->{file.filename}):",e)
else:
raise HTTPException(status_code=400, detail=f"File {file.filename} is not an Excel file")
raise APIException('kintone:updateappfromexcel',request.url._url, f"File {file.filename} is not an Excel file",e)
return result
@r.post("/updateprocessfromexcel",)
async def updateprocessfromexcel(app:str):
async def updateprocessfromexcel(request:Request,app:str,env = Depends(getkintoneenv)):
try:
excel = getprocessfromexcel()
kintone = getprocessfromkintone(app)
kintone = getprocessfromkintone(app,env)
revision = kintone["revision"]
#fields = analysefields(excel,kintone["properties"])
result = {"app":app,"revision":revision,"msg":"No Update"}
@@ -419,14 +692,32 @@ async def updateprocessfromexcel(app:str):
# result = updateappsettingstokintone(app,settings)
# revision = result["revision"]
# deploy = True
result = updateprocesstokintone(app,excel)
result = updateprocesstokintone(app,excel,env)
revision = result["revision"]
deploy = True
if deploy:
result = deoployappfromkintone(app,revision)
result = deoployappfromkintone(app,revision,env)
except Exception as e:
raise HTTPException(status_code=400, detail=f"Error occurred : {str(e)}")
raise APIException('kintone:updateprocessfromexcel',request.url._url, f"Error occurred while update process ({env.DOMAIN_NAME}->{app}):",e)
return result
@r.post("/createjstokintone",)
async def createjstokintone(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv)):
try:
jscs=[]
files=[]
files.append(createappjs(env.DOMAIN_ID, app))
files.append(getTempPath('alc_runtime.js'))
files.append(getTempPath('alc_runtime.css'))
for file in files:
upload = uploadkintonefiles(file,env)
if upload.get('fileKey') != None:
jscs.append({ file :upload['fileKey']})
appjscs = updateappjscss(app,jscs,env)
if appjscs.get("revision") != None:
deoployappfromkintone(app,appjscs["revision"],env)
return appjscs
except Exception as e:
raise APIException('kintone:createjstokintone',request.url._url, f"Error occurred while create js ({env.DOMAIN_NAME}->{app}):",e)

View File

@@ -4,6 +4,8 @@ from app.db.session import get_db
from app.db.crud import *
from app.db.schemas import *
from typing import List
from app.core.auth import get_current_active_user,get_current_user
from app.core.apiexception import APIException
platform_router = r = APIRouter()
@@ -17,9 +19,11 @@ async def appsetting_details(
id: int,
db=Depends(get_db),
):
app = get_appsetting(db, id)
return app
try:
app = get_appsetting(db, id)
return app
except Exception as e:
raise APIException('platform:appsettings',request.url._url,f"Error occurred while get app setting:",e)
@r.post("/appsettings", response_model=App, response_model_exclude_none=True)
async def appsetting_create(
@@ -27,7 +31,10 @@ async def appsetting_create(
app: AppBase,
db=Depends(get_db),
):
return create_appsetting(db, app)
try:
return create_appsetting(db, app)
except Exception as e:
raise APIException('platform:appsettings',request.url._url,f"Error occurred while get create app setting:",e)
@r.put(
@@ -39,7 +46,10 @@ async def appsetting_edit(
app: AppEdit,
db=Depends(get_db),
):
return edit_appsetting(db, id, app)
try:
return edit_appsetting(db, id, app)
except Exception as e:
raise APIException('platform:appsettings',request.url._url,f"Error occurred while edit app setting:",e)
@r.delete(
@@ -50,8 +60,10 @@ async def appsettings_delete(
id: int,
db=Depends(get_db),
):
return delete_appsetting(db, id)
try:
return delete_appsetting(db, id)
except Exception as e:
raise APIException('platform:appsettings',request.url._url,f"Error occurred while delete app setting:",e)
@r.get(
@@ -64,8 +76,11 @@ async def kintone_data(
type: int,
db=Depends(get_db),
):
kintone = get_kintones(db, type)
return kintone
try:
kintone = get_kintones(db, type)
return kintone
except Exception as e:
raise APIException('platform:kintone',request.url._url,f"Error occurred while get kintone env:",e)
@r.get(
"/actions",
@@ -77,8 +92,11 @@ async def action_data(
request: Request,
db=Depends(get_db),
):
actions = get_actions(db)
return actions
try:
actions = get_actions(db)
return actions
except Exception as e:
raise APIException('platform:actions',request.url._url,f"Error occurred while get actions:",e)
@r.get(
"/flow/{flowid}",
@@ -90,8 +108,11 @@ async def flow_details(
flowid: str,
db=Depends(get_db),
):
app = get_flow(db, flowid)
return app
try:
app = get_flow(db, flowid)
return app
except Exception as e:
raise APIException('platform:flow',request.url._url,f"Error occurred while get flow by flowid:",e)
@r.get(
@@ -102,19 +123,30 @@ async def flow_details(
async def flow_list(
request: Request,
appid: str,
user=Depends(get_current_user),
db=Depends(get_db),
):
flows = get_flows_by_app(db, appid)
return flows
try:
domain = get_activedomain(db, user.id)
print("domain=>",domain)
flows = get_flows_by_app(db, domain.id, appid)
return flows
except Exception as e:
raise APIException('platform:flow',request.url._url,f"Error occurred while get flow by appid:",e)
@r.post("/flow", response_model=Flow, response_model_exclude_none=True)
async def flow_create(
request: Request,
flow: FlowBase,
user=Depends(get_current_user),
db=Depends(get_db),
):
return create_flow(db, flow)
try:
domain = get_activedomain(db, user.id)
return create_flow(db, domain.id, flow)
except Exception as e:
raise APIException('platform:flow',request.url._url,f"Error occurred while create flow:",e)
@r.put(
@@ -125,7 +157,10 @@ async def flow_edit(
flow: FlowBase,
db=Depends(get_db),
):
return edit_flow(db, flow)
try:
return edit_flow(db, flow)
except Exception as e:
raise APIException('platform:flow',request.url._url,f"Error occurred while edit flow:",e)
@r.delete(
@@ -136,5 +171,175 @@ async def flow_delete(
flowid: str,
db=Depends(get_db),
):
try:
return delete_flow(db, flowid)
except Exception as e:
raise APIException('platform:flow',request.url._url,f"Error occurred while delete flow:",e)
return delete_flow(db, flowid)
@r.get(
"/domains/{tenantid}",
response_model=List[Domain],
response_model_exclude_none=True,
)
async def domain_details(
request: Request,
tenantid:str,
db=Depends(get_db),
):
try:
domains = get_domains(db,tenantid)
return domains
except Exception as e:
raise APIException('platform:domains',request.url._url,f"Error occurred while get domains:",e)
@r.post("/domain", response_model=Domain, response_model_exclude_none=True)
async def domain_create(
request: Request,
domain: DomainBase,
db=Depends(get_db),
):
try:
return create_domain(db, domain)
except Exception as e:
raise APIException('platform:domain',request.url._url,f"Error occurred while create domain:",e)
@r.put(
"/domain", response_model=Domain, response_model_exclude_none=True
)
async def domain_edit(
request: Request,
domain: DomainBase,
db=Depends(get_db),
):
try:
return edit_domain(db, domain)
except Exception as e:
raise APIException('platform:domain',request.url._url,f"Error occurred while edit domain:",e)
@r.delete(
"/domain/{id}", response_model=Domain, response_model_exclude_none=True
)
async def domain_delete(
request: Request,
id: int,
db=Depends(get_db),
):
try:
return delete_domain(db,id)
except Exception as e:
raise APIException('platform:domain',request.url._url,f"Error occurred while delete domain:",e)
@r.get(
"/domain",
response_model=List[Domain],
response_model_exclude_none=True,
)
async def userdomain_details(
request: Request,
user=Depends(get_current_user),
db=Depends(get_db),
):
try:
domains = get_domain(db, user.id)
return domains
except Exception as e:
raise APIException('platform:domain',request.url._url,f"Error occurred while get user({user.id}) domain:",e)
@r.post(
"/domain/{userid}",
response_model_exclude_none=True,
)
async def create_userdomain(
request: Request,
userid: int,
domainids:list,
db=Depends(get_db),
):
try:
domain = add_userdomain(db, userid,domainids)
return domain
except Exception as e:
raise APIException('platform:domain',request.url._url,f"Error occurred while add user({userid}) domain:",e)
@r.delete(
"/domain/{domainid}/{userid}", response_model_exclude_none=True
)
async def userdomain_delete(
request: Request,
domainid:int,
userid: int,
db=Depends(get_db),
):
try:
return delete_userdomain(db, userid,domainid)
except Exception as e:
raise APIException('platform:delete',request.url._url,f"Error occurred while delete user({userid}) domain:",e)
@r.get(
"/activedomain",
response_model=Domain,
response_model_exclude_none=True,
)
async def get_useractivedomain(
request: Request,
user=Depends(get_current_user),
db=Depends(get_db),
):
try:
domain = get_activedomain(db, user.id)
return domain
except Exception as e:
raise APIException('platform:activedomain',request.url._url,f"Error occurred while get user({user.id}) activedomain:",e)
@r.put(
"/activedomain/{domainid}",
response_model_exclude_none=True,
)
async def update_activeuserdomain(
request: Request,
domainid:int,
user=Depends(get_current_user),
db=Depends(get_db),
):
try:
domain = active_userdomain(db, user.id,domainid)
return domain
except Exception as e:
raise APIException('platform:activedomain',request.url._url,f"Error occurred while update user({user.id}) activedomain:",e)
@r.get(
"/events",
response_model=t.List[Event],
response_model_exclude={"id"},
response_model_exclude_none=True,
)
async def event_data(
request: Request,
db=Depends(get_db),
):
try:
events = get_events(db)
return events
except Exception as e:
raise APIException('platform:events',request.url._url,f"Error occurred while get events:",e)
@r.get(
"/eventactions/{eventid}",
response_model=t.List[Action],
response_model_exclude={"id"},
response_model_exclude_none=True,
)
async def eventactions_data(
request: Request,
eventid: str,
db=Depends(get_db),
):
try:
actions = get_eventactions(db,eventid)
return actions
except Exception as e:
raise APIException('platform:eventactions',request.url._url,f"Error occurred while get eventactions:",e)

View File

@@ -0,0 +1,26 @@
from fastapi import HTTPException, status
from app.db.schemas import ErrorCreate
from app.db.session import SessionLocal
from app.db.crud import create_log
class APIException(Exception):
def __init__(self,location:str,title:str,content:str,e:Exception):
if(str(e) == ''):
content += e.detail
self.detail = e.detail
self.status_code = e.status_code
else:
self.detail = str(e)
content += str(e)
self.status_code = 500
if(len(content) > 5000):
content =content[0:5000]
self.error = ErrorCreate(location=location,title=title,content=content)
def writedblog(exc: APIException):
db = SessionLocal()
try:
create_log(db,exc.error)
finally:
db.close()

View File

@@ -3,7 +3,7 @@ from fastapi import Depends, HTTPException, status
from jwt import PyJWTError
from app.db import models, schemas, session
from app.db.crud import get_user_by_email, create_user
from app.db.crud import get_user_by_email, create_user,get_user
from app.core import security
@@ -19,14 +19,14 @@ async def get_current_user(
payload = jwt.decode(
token, security.SECRET_KEY, algorithms=[security.ALGORITHM]
)
email: str = payload.get("sub")
if email is None:
id: int = payload.get("sub")
if id is None:
raise credentials_exception
permissions: str = payload.get("permissions")
token_data = schemas.TokenData(email=email, permissions=permissions)
token_data = schemas.TokenData(id = id, permissions=permissions)
except PyJWTError:
raise credentials_exception
user = get_user_by_email(db, token_data.email)
user = get_user(db, token_data.id)
if user is None:
raise credentials_exception
return user
@@ -58,7 +58,7 @@ def authenticate_user(db, email: str, password: str):
return user
def sign_up_new_user(db, email: str, password: str):
def sign_up_new_user(db, email: str, password: str, firstname: str,lastname: str):
user = get_user_by_email(db, email)
if user:
return False # User already exists
@@ -67,6 +67,8 @@ def sign_up_new_user(db, email: str, password: str):
schemas.UserCreate(
email=email,
password=password,
first_name = firstname,
last_name = lastname,
is_active=True,
is_superuser=False,
),

View File

@@ -1,18 +1,38 @@
import os
import base64
PROJECT_NAME = "KintoneAppBuilder"
SQLALCHEMY_DATABASE_URI = "mssql+pymssql://maxz64@maxzdb:m@xz1205@maxzdb.database.windows.net/alloc"
BASE_URL = "https://mfu07rkgnb7c.cybozu.com"
#SQLALCHEMY_DATABASE_URI = "postgres://maxz64:m@xz1205@alicornkintone.postgres.database.azure.com/postgres"
SQLALCHEMY_DATABASE_URI = "postgres://kabAdmin:P@ssw0rd!@kintonetooldb.postgres.database.azure.com/postgres"
#SQLALCHEMY_DATABASE_URI = "postgres://kabAdmin:P@ssw0rd!@kintonetooldb.postgres.database.azure.com/unittest"
API_V1_STR = "/k/v1"
API_V1_AUTH_KEY = "X-Cybozu-Authorization"
API_V1_AUTH_VALUE = "TVhaOm1heHoxMjA1"
DEPLOY_MODE = "PROD" #DEV,PROD
KINTONE_USER = "MXZ"
DEPLOY_JS_URL = "https://ka-addin.azurewebsites.net/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"]
KINTONE_FIELD_PROPERTY=['label','code','type','required','unique','maxValue','minValue','maxLength','minLength','defaultValue','defaultNowValue','options','expression','hideExpression','digit','protocol','displayScale','unit','unitPosition']
class KINTONE_ENV:
BASE_URL = ""
API_V1_AUTH_VALUE = ""
KINTONE_USER = ""
DOMAIN_ID = ""
DOMAIN_NAME =""
def __init__(self,domain) -> None:
self.DOMAIN_NAME=domain.name
self.DOMAIN_ID=domain.id
self.BASE_URL = domain.url
self.KINTONE_USER = domain.kintoneuser
self.API_V1_AUTH_VALUE = base64.b64encode(bytes(f"{domain.kintoneuser}:{domain.kintonepwd}","utf-8"))

View File

@@ -9,7 +9,7 @@ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
SECRET_KEY = "alicorns"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
ACCESS_TOKEN_EXPIRE_MINUTES = 2880
def get_password_hash(password: str) -> str:
@@ -25,7 +25,7 @@ def create_access_token(*, data: dict, expires_delta: timedelta = None):
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt

View File

@@ -1,5 +1,6 @@
from fastapi import HTTPException, status
from sqlalchemy.orm import Session
from sqlalchemy import and_
import typing as t
from . import models, schemas
@@ -124,11 +125,12 @@ def get_actions(db: Session):
return actions
def create_flow(db: Session, flow: schemas.FlowBase):
def create_flow(db: Session, domainid: int, flow: schemas.FlowBase):
db_flow = models.Flow(
flowid=flow.flowid,
appid=flow.appid,
eventid=flow.eventid,
domainid=domainid,
name=flow.name,
content=flow.content
)
@@ -175,8 +177,122 @@ def get_flow(db: Session, flowid: str):
raise HTTPException(status_code=404, detail="Data not found")
return flow
def get_flows_by_app(db: Session, appid: str):
flows = db.query(models.Flow).filter(models.Flow.appid == appid).all()
def get_flows_by_app(db: Session, domainid: int, appid: str):
flows = db.query(models.Flow).filter(and_(models.Flow.domainid == domainid,models.Flow.appid == appid)).all()
if not flows:
raise HTTPException(status_code=404, detail="Data not found")
raise Exception("Data not found")
return flows
def create_domain(db: Session, domain: schemas.DomainBase):
db_domain = models.Domain(
tenantid = domain.tenantid,
name=domain.name,
url=domain.url,
kintoneuser=domain.kintoneuser,
kintonepwd=domain.kintonepwd
)
db.add(db_domain)
db.commit()
db.refresh(db_domain)
return db_domain
def delete_domain(db: Session,id: int):
db_domain = db.query(models.Domain).get(id)
if not db_domain:
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
db.delete(db_domain)
db.commit()
return db_domain
def edit_domain(
db: Session, domain: schemas.DomainBase
) -> schemas.Domain:
db_domain = db.query(models.Domain).get(domain.id)
if not db_domain:
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
update_data = domain.dict(exclude_unset=True)
for key, value in update_data.items():
if(key != "id"):
setattr(db_domain, key, value)
db.add(db_domain)
db.commit()
db.refresh(db_domain)
return db_domain
def add_userdomain(db: Session, userid:int,domainids:list):
for domainid in domainids:
db_domain = models.UserDomain(
userid = userid,
domainid = domainid
)
db.add(db_domain)
db.commit()
db.refresh(db_domain)
return db_domain
def delete_userdomain(db: Session, userid: int,domainid: int):
db_domain = db.query(models.UserDomain).filter(and_(models.UserDomain.userid == userid,models.UserDomain.domainid == domainid)).first()
if not db_domain:
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
db.delete(db_domain)
db.commit()
return db_domain
def active_userdomain(db: Session, userid: int,domainid: int):
db_userdomains = db.query(models.UserDomain).filter(models.UserDomain.userid == userid).all()
if not db_userdomains:
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
for domain in db_userdomains:
if domain.domainid == domainid:
domain.active = True
else:
domain.active = False
db.add(domain)
db.commit()
return db_userdomains
def get_activedomain(db: Session, userid: int):
db_domain = db.query(models.Domain).join(models.UserDomain,models.UserDomain.domainid == models.Domain.id ).filter(and_(models.UserDomain.userid == userid,models.UserDomain.active == True)).first()
if not db_domain:
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
return db_domain
def get_domain(db: Session, userid: str):
domains = db.query(models.Domain).join(models.UserDomain,models.UserDomain.domainid == models.Domain.id ).filter(models.UserDomain.userid == userid).all()
if not domains:
raise HTTPException(status_code=404, detail="Data not found")
return domains
def get_domains(db: Session,tenantid:str):
domains = db.query(models.Domain).filter(models.Domain.tenantid == tenantid ).all()
if not domains:
raise HTTPException(status_code=404, detail="Data not found")
return domains
def get_events(db: Session):
events = db.query(models.Event).all()
if not events:
raise HTTPException(status_code=404, detail="Data not found")
return events
def get_eventactions(db: Session,eventid: str):
#eveactions = db.query(models.Action).join(models.EventAction,models.EventAction.actionid == models.Action.id ).join(models.Event,models.Event.id == models.EventAction.eventid).filter(models.Event.eventid == eventid).all()
eveactions = db.query(models.Action).join(models.EventAction,models.EventAction.actionid != models.Action.id and models.EventAction.eventid == eventid ).join(models.Event,models.Event.id == models.EventAction.eventid).filter(models.Event.eventid == eventid).all()
if not eveactions:
raise HTTPException(status_code=404, detail="Data not found")
return eveactions
def create_log(db: Session, error:schemas.ErrorCreate):
db_log = models.ErrorLog(title=error.title,location=error.location,content=error.content)
db.add(db_log)
db.commit()
db.refresh(db_log)
return db_log
def get_kintoneformat(db: Session):
formats = db.query(models.KintoneFormat).order_by(models.KintoneFormat.id).all()
return formats

View File

@@ -1,4 +1,4 @@
from sqlalchemy import Boolean, Column, Integer, String, DateTime
from sqlalchemy import Boolean, Column, Integer, String, DateTime,ForeignKey
from sqlalchemy.ext.declarative import as_declarative
from datetime import datetime
@@ -47,5 +47,67 @@ class Flow(Base):
flowid = Column(String(100), index=True, nullable=False)
appid = Column(String(100), index=True, nullable=False)
eventid = Column(String(100), index=True, nullable=False)
domainid = Column(Integer,ForeignKey("domain.id"))
name = Column(String(200))
content = Column(String)
class Tenant(Base):
__tablename__ = "tenant"
tenantid = Column(String(100), index=True, nullable=False)
name = Column(String(200))
licence = Column(String(200))
startdate = Column(DateTime)
enddate = Column(DateTime)
class Domain(Base):
__tablename__ = "domain"
tenantid = Column(String(100), index=True, nullable=False)
name = Column(String(100), nullable=False)
url = Column(String(200), nullable=False)
kintoneuser = Column(String(100), nullable=False)
kintonepwd = Column(String(100), nullable=False)
class UserDomain(Base):
__tablename__ = "userdomain"
userid = Column(Integer,ForeignKey("user.id"))
domainid = Column(Integer,ForeignKey("domain.id"))
active = Column(Boolean, default=False)
class Event(Base):
__tablename__ = "event"
category = Column(String(100), nullable=False)
type = Column(String(100), nullable=False)
eventid= Column(String(100), nullable=False)
function = Column(String(500), nullable=False)
mobile = Column(Boolean, default=False)
eventgroup = Column(Boolean, default=False)
class EventAction(Base):
__tablename__ = "eventaction"
eventid = Column(Integer,ForeignKey("event.id"))
actionid = Column(Integer,ForeignKey("action.id"))
class ErrorLog(Base):
__tablename__ = "errorlog"
title = Column(String(50))
location = Column(String(500))
content = Column(String(5000))
class KintoneFormat(Base):
__tablename__ = "kintoneformat"
name = Column(String(50))
startrow =Column(Integer)
startcolumn =Column(Integer)
typecolumn =Column(Integer)
codecolumn =Column(Integer)
field = Column(String(5000))
trueformat = Column(String(10))

View File

@@ -20,7 +20,12 @@ class UserOut(UserBase):
class UserCreate(UserBase):
email:str
password: str
first_name: str
last_name: str
is_active:bool
is_superuser:bool
class Config:
orm_mode = True
@@ -46,6 +51,7 @@ class Token(BaseModel):
class TokenData(BaseModel):
id:int = 0
email: str = None
permissions: str = "user"
@@ -98,8 +104,45 @@ class Flow(Base):
flowid: str
appid: str
eventid: str
domainid: int
name: str = None
content: str = None
class Config:
orm_mode = True
class DomainBase(BaseModel):
id: int
tenantid: str
name: str
url: str
kintoneuser: str
kintonepwd: str
class Domain(Base):
id: int
tenantid: str
name: str
url: str
kintoneuser: str
kintonepwd: str
class Config:
orm_mode = True
class Event(Base):
id: int
category: str
type: str
eventid: str
function: str
mobile: bool
eventgroup: bool
class Config:
orm_mode = True
class ErrorCreate(BaseModel):
title:str
location:str
content:str

View File

@@ -1,3 +1,4 @@
import os
from fastapi import FastAPI, Depends
from starlette.requests import Request
import uvicorn
@@ -11,6 +12,11 @@ from app.core.auth import get_current_active_user
from app.core.celery_app import celery_app
from app import tasks
from fastapi.middleware.cors import CORSMiddleware
import logging
from app.core.apiexception import APIException, writedblog
from app.db.crud import create_log
from fastapi.responses import JSONResponse
import asyncio
Base.metadata.create_all(bind=engine)
@@ -19,9 +25,7 @@ app = FastAPI(
)
origins = [
"http://localhost:9000",
"http://localhost",
"http://localhost:8080",
"*"
]
app.add_middleware(
@@ -39,6 +43,25 @@ app.add_middleware(
# request.state.db.close()
# return response
@app.on_event("startup")
async def startup_event():
log_dir="log"
if not os.path.exists(log_dir):
os.makedirs(log_dir)
logger = logging.getLogger("uvicorn.access")
handler = logging.handlers.RotatingFileHandler(f"{log_dir}/api.log",mode="a",maxBytes = 100*1024, backupCount = 3)
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
logger.addHandler(handler)
@app.exception_handler(APIException)
async def api_exception_handler(request: Request, exc: APIException):
loop = asyncio.get_event_loop()
loop.run_in_executor(None,writedblog,exc)
return JSONResponse(
status_code=exc.status_code,
content={"detail": f"{exc.detail}"},
)
@app.get("/api/v1")
async def root():

View File

@@ -25,3 +25,7 @@ python -m venv env
pip install -r requirements.txt
```
4. backend 起動
```bash
uvicorn app.main:app --reload
```

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@@ -0,0 +1,147 @@
<mxfile host="app.diagrams.net" modified="2024-02-21T05:42:02.026Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36" etag="T2S5cjvthSOlO5DmGw-C" version="23.1.5" type="device">
<diagram id="Z6uZM46JtkVaKDzPjE9h" name="サイトマップ">
<mxGraphModel dx="1434" dy="820" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="Gi77RX5G2m4J9-6cMje4-14" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-1" target="Gi77RX5G2m4J9-6cMje4-13" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-1" value="テナント登録" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.login;" parent="1" vertex="1">
<mxGeometry x="60" y="50" width="120" height="70" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-2" value="Admin Login" style="html=1;whiteSpace=wrap;strokeColor=#2D7600;fillColor=#60a917;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#ffffff;sketch=0;shape=mxgraph.sitemap.login;" parent="1" vertex="1">
<mxGeometry x="60" y="270" width="120" height="70" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-8" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-5" target="Gi77RX5G2m4J9-6cMje4-7" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-9" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-5" target="Gi77RX5G2m4J9-6cMje4-7" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-12" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-5" target="Gi77RX5G2m4J9-6cMje4-11" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-5" value="Home" style="html=1;whiteSpace=wrap;strokeColor=#2D7600;fillColor=#60a917;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#ffffff;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
<mxGeometry x="240" y="270" width="120" height="70" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-6" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-2" target="Gi77RX5G2m4J9-6cMje4-5" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-42" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-7" target="Gi77RX5G2m4J9-6cMje4-41" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-7" value="ユーザー登録" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
<mxGeometry x="440" y="220" width="120" height="70" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-11" value="ドメイン登録" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
<mxGeometry x="440" y="340" width="120" height="70" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-13" value="テナント管理者作成" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.login;" parent="1" vertex="1">
<mxGeometry x="240" y="50" width="120" height="70" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-15" value="ライセンス情報" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" parent="1" vertex="1">
<mxGeometry x="480" y="10" width="90" height="70" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-16" value="Adminユーザー" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" parent="1" vertex="1">
<mxGeometry x="480" y="90" width="90" height="70" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-17" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-13" target="Gi77RX5G2m4J9-6cMje4-15" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-18" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-13" target="Gi77RX5G2m4J9-6cMje4-16" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-19" value="テナントDB&lt;br&gt;作成" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" parent="1" vertex="1">
<mxGeometry x="550" y="50" width="90" height="70" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-22" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-20" target="Gi77RX5G2m4J9-6cMje4-21" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-20" value="Login" style="html=1;whiteSpace=wrap;strokeColor=#005700;fillColor=#008a00;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;sketch=0;shape=mxgraph.sitemap.login;fontColor=#ffffff;" parent="1" vertex="1">
<mxGeometry x="50" y="610" width="120" height="70" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-24" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-21" target="Gi77RX5G2m4J9-6cMje4-25" edge="1">
<mxGeometry relative="1" as="geometry">
<mxPoint x="430" y="645" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-21" value="Home" style="html=1;whiteSpace=wrap;strokeColor=#005700;fillColor=#008a00;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#ffffff;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
<mxGeometry x="230" y="610" width="120" height="70" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-27" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-25" target="Gi77RX5G2m4J9-6cMje4-26" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-25" value="アプリ一覧" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.news;" parent="1" vertex="1">
<mxGeometry x="440" y="610" width="120" height="70" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-29" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-26" target="Gi77RX5G2m4J9-6cMje4-28" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-26" value="フロー一覧" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.news;" parent="1" vertex="1">
<mxGeometry x="620" y="610" width="120" height="70" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-40" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-28" target="Gi77RX5G2m4J9-6cMje4-39" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-28" value="フローエディタ" style="html=1;whiteSpace=wrap;strokeColor=#005700;fillColor=#008a00;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#ffffff;sketch=0;shape=mxgraph.sitemap.news;" parent="1" vertex="1">
<mxGeometry x="800" y="610" width="120" height="70" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-33" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-30" target="Gi77RX5G2m4J9-6cMje4-32" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-30" value="設計書取込" style="html=1;whiteSpace=wrap;strokeColor=#005700;fillColor=#008a00;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#ffffff;sketch=0;shape=mxgraph.sitemap.news;" parent="1" vertex="1">
<mxGeometry x="440" y="715" width="120" height="70" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-31" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-21" target="Gi77RX5G2m4J9-6cMje4-30" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-32" value="取込結果表示" style="html=1;whiteSpace=wrap;strokeColor=#005700;fillColor=#008a00;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#ffffff;sketch=0;shape=mxgraph.sitemap.news;" parent="1" vertex="1">
<mxGeometry x="620" y="715" width="120" height="70" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-37" value="設計書ダウロード" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.news;" parent="1" vertex="1">
<mxGeometry x="440" y="825" width="120" height="70" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-38" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-21" target="Gi77RX5G2m4J9-6cMje4-37" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-39" value="フロー履歴管理" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.news;" parent="1" vertex="1">
<mxGeometry x="980" y="610" width="120" height="70" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-41" value="ALC設定" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
<mxGeometry x="620" y="220" width="120" height="70" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-43" value="管理ドメイン設定" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
<mxGeometry x="800" y="220" width="120" height="70" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-44" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-41" target="Gi77RX5G2m4J9-6cMje4-43" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-45" value="プロファイル" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
<mxGeometry x="440" y="935" width="120" height="70" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-46" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-21" target="Gi77RX5G2m4J9-6cMje4-45" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-50" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-47" target="Gi77RX5G2m4J9-6cMje4-49" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-47" value="プロファイル" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
<mxGeometry x="440" y="450" width="120" height="70" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-48" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-5" target="Gi77RX5G2m4J9-6cMje4-47" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-49" value="ライセンス情報" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
<mxGeometry x="620" y="450" width="120" height="70" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-51" value="ライセンス情報" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
<mxGeometry x="620" y="935" width="120" height="70" as="geometry" />
</mxCell>
<mxCell id="Gi77RX5G2m4J9-6cMje4-52" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-45" target="Gi77RX5G2m4J9-6cMje4-51" edge="1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

View File

@@ -1,2 +1,6 @@
KAB_BACKEND_URL="http://127.0.0.1:8000/api/v1/"
#開発環境
KAB_BACKEND_URL="https://kab-backend.azurewebsites.net/"
#単体テスト環境
#KAB_BACKEND_URL="https://kab-backend-unittest.azurewebsites.net/"
#ローカル開発環境
#KAB_BACKEND_URL="http://127.0.0.1:8000/"

View File

@@ -1,2 +1,2 @@
VUE_BACKEND_URL="http://localhost:8000/api/"
#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

@@ -1,8 +1,8 @@
{
"name": "kintone-app-builder",
"version": "0.0.1",
"name": "kintone-automate",
"version": "0.2.0",
"description": "Kintoneアプリの自動生成とデプロイを支援ツールです",
"productName": "Kintone App Builder",
"productName": "kintone Automate",
"author": "maxiaozhe@alicorns.co.jp <maxiaozhe@alicorns.co.jp>",
"private": true,
"scripts": {
@@ -10,12 +10,16 @@
"format": "prettier --write \"**/*.{js,ts,vue,scss,html,md,json}\" --ignore-path .gitignore",
"test": "echo \"No test specified\" && exit 0",
"dev": "quasar dev",
"build": "quasar build"
"dev:local": "set \"LOCAL=true\" && quasar dev",
"build": "set \"SOURCE_MAP=false\" && quasar build",
"build:dev":"set \"SOURCE_MAP=true\" && quasar build"
},
"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

@@ -0,0 +1,8 @@
<configuration>
<system.webServer>
<staticContent>
<mimeMap fileExtension=".woff" mimeType="application/font-woff" />
<mimeMap fileExtension=".woff2" mimeType="application/font-woff2" />
</staticContent>
</system.webServer>
</configuration>

View File

@@ -10,10 +10,14 @@
const { configure } = require('quasar/wrappers');
const dotenv = require('dotenv').config().parsed;
const package = require('./package.json');
const envPath = process.env.LOCAL==='true'?'.env.development':'.env';
const dotenv = require('dotenv').config({path:envPath}).parsed;
console.log('dotenv=>',dotenv);
// const package = require('./package.json');
const { Notify } = require('quasar');
const version = package.version;
const version = process.env.npm_package_version;
const productName=process.env.npm_package_productName;
// console.log(process.env);
module.exports = configure(function (/* ctx */) {
return {
eslint: {
@@ -32,7 +36,8 @@ module.exports = configure(function (/* ctx */) {
// --> boot files are part of "main.js"
// https://v2.quasar.dev/quasar-cli-vite/boot-files
boot: [
'axios'
'axios',
'error-handler'
],
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
@@ -49,7 +54,6 @@ module.exports = configure(function (/* ctx */) {
// 'themify',
// 'line-awesome',
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
'roboto-font', // optional, you are not bound to it
'material-icons', // optional, you are not bound to it
],
@@ -60,6 +64,7 @@ module.exports = configure(function (/* ctx */) {
browser: ['es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1'],
node: 'node16'
},
sourcemap:process.env.SOURCE_MAP === 'true',
vueRouterMode: 'hash', // available values: 'hash', 'history'
// vueRouterBase,
@@ -70,7 +75,7 @@ module.exports = configure(function (/* ctx */) {
// publicPath: '/',
// analyze: true,
env: { ...dotenv, version },
env: { ...dotenv, version ,productName},
// rawDefine: {}
// ignorePublicFolder: true,
// minify: false,
@@ -89,6 +94,7 @@ module.exports = configure(function (/* ctx */) {
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
devServer: {
// https: true
port:9001,
open: true, // opens browser window automatically
env: { ...dotenv },
},

View File

@@ -1,5 +1,6 @@
import { boot } from 'quasar/wrappers';
import axios, { AxiosInstance } from 'axios';
import {router} from 'src/router';
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
@@ -15,7 +16,26 @@ declare module '@vue/runtime-core' {
// "export default () => {}" function below (which runs individually
// for each client)
const api:AxiosInstance = axios.create({ baseURL: process.env.KAB_BACKEND_URL });
const token=localStorage.getItem('token')||'';
if(token!==''){
api.defaults.headers["Authorization"]='Bearer ' + token;
}
//axios例外キャプチャー
api.interceptors.response.use(
(response)=>response,
(error)=>{
if (error.response && error.response.status === 401) {
// 認証エラーの場合再ログインする
console.error('(; ゚Д゚)/認証エラー(401)', error);
localStorage.removeItem('token');
router.replace({
path:"/login",
query:{redirect:router.currentRoute.value.fullPath}
});
}
return Promise.reject(error);
}
)
export default boot(({ app }) => {
// for use inside Vue files (Options API) through this.$axios and this.$api

View File

@@ -0,0 +1,21 @@
// src/boot/error-handler.ts
import { boot } from 'quasar/wrappers';
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) {
// 認証エラーの場合再ログインする
console.error('(; ゚Д゚)/認証エラー(401)', err, info);
localStorage.removeItem('token');
router.replace({
path:"/login",
query:{redirect:router.currentRoute.value.fullPath}
});
} else {
console.error('(; ゚Д゚)例外:', err, info);
}
};
});

View File

@@ -1,12 +1,17 @@
<template>
<div class="q-pa-md">
<q-table row-key="name" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows"
<div v-if="!isLoaded" class="spinner flex flex-center">
<q-spinner color="primary" size="3em" />
</div>
<q-table v-else row-key="index" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows"
class="action-table"
flat bordered
virtual-scroll
:pagination="pagination"
:rows-per-page-options="[0]"
/>
:filter="filter"
>
</q-table>
</div>
</template>
<script>
@@ -17,23 +22,24 @@ export default {
name: 'actionSelect',
props: {
name: String,
type: String
type: String,
filter:String
},
setup() {
const columns = [
{ name: 'name', required: true,label: 'アクション名',align: 'left',field: 'name',sortable: true},
{ name: 'desc', align: 'left', label: '説明', field: 'desc', sortable: true },
// { name: 'content', label: '内容', field: 'content', sortable: true }
]
const rows = reactive([])
setup(props) {
const isLoaded=ref(false);
const columns = [
{ name: 'name', required: true,label: 'アクション名',align: 'left',field: 'name',sortable: true},
{ name: 'desc', align: 'left', label: '説明', field: 'desc', sortable: true },
// { name: 'content', label: '内容', field: 'content', sortable: true }
];
const rows = reactive([])
onMounted(async () => {
await api.get('http://127.0.0.1:8000/api/kintone/1').then(res =>{
res.data.forEach((item) =>
const res =await api.get('api/actions');
res.data.forEach((item,index) =>
{
rows.push({name:item.name,desc:item.desc,content:item.content});
}
)
});
rows.push({index,name:item.name,desc:item.title,outputPoints:item.outputpoints,property:item.property});
});
isLoaded.value=true;
});
return {
columns,
@@ -41,7 +47,8 @@ export default {
selected: ref([]),
pagination:ref({
rowsPerPage:0
})
}),
isLoaded,
}
},
@@ -49,6 +56,7 @@ export default {
</script>
<style lang="scss">
.action-table{
height: 100%;
min-height: 10vh;
max-height: 68vh;
}
</style>

View File

@@ -0,0 +1,131 @@
<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" :updateSelectFields="updateSelectFields"
:appId="selField?.app?.id" not_page :filter="fieldFilter"
:selectedFields="selField.fields" :fieldTypes="fieldTypes"></field-select>
</div>
</div>
</div>
<div style="min-width: 45vw;" v-else>
</div>
<show-dialog v-model:visible="showSelectApp" name="アプリ選択">
<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"
:updateSelectApp="updateSelectApp"></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 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'
},
fieldTypes:{
type:Array,
default:()=>[]
}
},
setup(props, { emit }) {
const showSelectApp = ref(false);
const selField = reactive(props.selectedField);
const isSelected = computed(() => {
return selField !== null && typeof selField === 'object' && ('app' in selField)
});
const updateSelectApp = (newAppinfo: IApp) => {
selField.app = newAppinfo
}
const updateSelectFields = (newFields: IField[]) => {
selField.fields = newFields
}
watchEffect(() => {
emit('update:modelValue', selField);
});
return {
showSelectApp,
isSelected,
updateSelectApp,
filter: ref(),
updateSelectFields,
fieldFilter: ref(),
selField
};
}
});
</script>

View File

@@ -36,7 +36,7 @@
import { AppInfo, AppSeed } from './models';
import { ref, defineComponent, watch, onMounted , toRefs } from 'vue';
import { api } from 'boot/axios';
import { promises } from 'dns';
import { useAuthStore } from 'src/stores/useAuthStore';
export default defineComponent({
props: {
@@ -44,12 +44,13 @@ export default defineComponent({
},
setup(props) {
const { app } = toRefs(props);
const authStore = useAuthStore();
const appinfo = ref<AppInfo>({
appId: "",
name: "",
description: ""
});
const link= ref('https://mfu07rkgnb7c.cybozu.com/k/' + app.value);
const link= ref(`${authStore.currentDomain.kintoneUrl}/k/${app.value}`);
const getAppInfo = async (appId:string|undefined) => {
if(!appId){
return;
@@ -59,7 +60,7 @@ export default defineComponent({
let retry =0;
while(retry<=3 && result && result.appId!==appId){
await new Promise(resolve => setTimeout(resolve, 1000));
const response = await api.get('app', {
const response = await api.get('api/v1/app', {
params:{
app: appId
}
@@ -73,7 +74,7 @@ export default defineComponent({
watch(app, async (newApp) => {
appinfo.value = await getAppInfo(newApp);
link.value = 'https://mfu07rkgnb7c.cybozu.com/k/' + newApp;
link.value = `${authStore.currentDomain.kintoneUrl}/k/${newApp}`;
}, { immediate: true });
const linkClick=(ev : MouseEvent)=>{
@@ -82,7 +83,7 @@ export default defineComponent({
};
onMounted(async ()=>{
appinfo.value = await getAppInfo(app.value);
link.value = 'https://mfu07rkgnb7c.cybozu.com/k/' + app.value;
link.value = `${authStore.currentDomain.kintoneUrl}/k/${app.value}`;
});
return {

View File

@@ -0,0 +1,100 @@
<template>
<div class="q-px-xs">
<div v-if="!isLoaded" class="spinner flex flex-center">
<q-spinner color="primary" size="3em" />
</div>
<q-table v-else class="app-table" :selection="type" row-key="id" v-model:selected="selected" flat bordered
virtual-scroll :columns="columns" :rows="rows" :pagination="pagination" :rows-per-page-options="[0]"
:filter="filter" style="max-height: 65vh;">
<template v-slot:body-cell-description="props">
<q-td :props="props">
<q-scroll-area class="description-cell">
<div v-html="props.row.description"></div>
</q-scroll-area>
</q-td>
</template>
</q-table>
</div>
</template>
<script lang="ts">
import { ref, onMounted, reactive, watchEffect } from 'vue'
import { api } from 'boot/axios';
export default {
name: 'AppSelectBox',
props: {
name: String,
type: String,
filter: String,
updateSelectApp: {
type: Function
}
},
setup(props) {
const columns = [
{ name: 'id', required: true, label: 'ID', align: 'left', field: 'id', sortable: true },
{ name: 'name', label: 'アプリ名', field: 'name', sortable: true, align: 'left' },
{ name: 'description', label: '概要', field: 'description', align: 'left', sortable: false },
{ name: 'createdate', label: '作成日時', field: 'createdate', align: 'left' }
]
const isLoaded = ref(false);
const rows: any[] = reactive([]);
const selected = ref([])
watchEffect(()=>{
if (selected.value && selected.value[0] && props.updateSelectApp) {
props.updateSelectApp(selected.value[0])
}
});
onMounted(() => {
api.get('api/v1/allapps').then(res => {
res.data.apps.forEach((item: any) => {
rows.push({
id: item.appId,
name: item.name,
description: item.description,
createdate: dateFormat(item.createdAt)
});
});
isLoaded.value = true;
});
});
const dateFormat = (dateStr: string) => {
const date = new Date(dateStr);
const pad = (num: number) => num.toString().padStart(2, '0');
const year = date.getFullYear();
const month = pad(date.getMonth() + 1);
const day = pad(date.getDate());
const hours = pad(date.getHours());
const minutes = pad(date.getMinutes());
const seconds = pad(date.getSeconds());
return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
}
return {
columns,
rows,
selected,
isLoaded,
pagination: ref({
rowsPerPage: 10
})
}
},
}
</script>
<style lang="scss">
.description-cell {
height: 60px;
width: 300px;
max-height: 60px;
max-width: 300px;
white-space: break-spaces;
}
.spinner {
min-height: 300px;
min-width: 400px;
}
</style>

View File

@@ -0,0 +1,107 @@
<template>
<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">
<q-list>
<q-item clickable @click="copyCondition()">
<q-item-section avatar><q-icon name="content_copy" ></q-icon></q-item-section>
<q-item-section >コピー</q-item-section>
</q-item>
<q-item clickable @click="pasteCondition()">
<q-item-section avatar><q-icon name="content_paste" ></q-icon></q-item-section>
<q-item-section >貼り付け</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</template>
<NodeCondition v-model:conditionTree="tree"></NodeCondition>
</show-dialog>
</template>
<script lang="ts">
import { defineComponent, ref ,watchEffect} from 'vue';
import ShowDialog from '../../components/ShowDialog.vue';
import NodeCondition from './NodeCondition.vue';
import { ConditionTree } from '../../types/Conditions';
import { useQuasar } from 'quasar';
export default defineComponent({
name: 'ConditionObject',
components: {
ShowDialog,
NodeCondition,
},
props: {
conditionTree: {
type: ConditionTree,
default: null
},
show:{
type:Boolean,
default:false
}
},
emits:[
"closed",
"update:conditionTree",
"update:show"
],
setup(props,context) {
const appDg = ref();
const $q=useQuasar();
const tree = ref(props.conditionTree);
const closeDg = (val:string) => {
if (val == 'OK') {
if(tree.value.root.children.length===0){
$q.notify({
type: 'negative',
message: `条件式を設定してください。`
});
}
context.emit("update:conditionTree",tree.value);
}
showflg.value=false;
context.emit("update:show",false);
context.emit("closed",val);
};
const showflg =ref(props.show);
//条件式をコピーする
const copyCondition=()=>{
if (navigator.clipboard) {
const jsonData=tree.value.toJson();
navigator.clipboard.writeText(jsonData).then(() => {
console.log('Text successfully copied to clipboard');
},
(err) => {
console.error('Error in copying text: ', err);
});
} else {
console.log('Clipboard API not available');
}
};
//条件式を貼り付ける
const pasteCondition=async ()=>{
try {
const text = await navigator.clipboard.readText();
console.log('Text from clipboard:', text);
tree.value.fromJson(text);
} catch (err) {
console.error('Failed to read text from clipboard: ', err);
throw err;
}
}
watchEffect(() => {
showflg.value=props.show;
});
return {
tree,
appDg,
closeDg,
showflg,
copyCondition,
pasteCondition
};
}
});
</script>

View File

@@ -0,0 +1,135 @@
<template>
<div class="q-pa-sm">
<q-field labelColor="primary" class="condition-object" dense outlined :label="label" :disable="disabled"
:clearable="isSelected">
<template v-slot:control>
<q-chip color="primary" text-color="white" v-if="isSelected && selectedObject.objectType==='field'" :dense="true" class="selected-obj">
{{ selectedObject.name }}
</q-chip>
<q-chip color="info" text-color="white" v-if="isSelected && selectedObject.objectType==='variable'" :dense="true" class="selected-obj">
{{ selectedObject.name.name }}
</q-chip>
<div v-if="isSelected && selectedObject.objectType==='text'">{{ selectedObject?.sharedText }}</div>
</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" 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>
<condition-objects ref="appDg" name="フィールド" type="single" :filter="filter" :appId="store.appInfo?.appId" :vars="vars"></condition-objects>
-->
<DynamicItemInput v-model:selectedObject="selectedObject" :canInput="config.canInput"
:buttonsConfig="config.buttonsConfig" :appId="store.appInfo?.appId" />
</show-dialog>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, ref, watchEffect, computed } from 'vue';
import ShowDialog from '../ShowDialog.vue';
// import ConditionObjects from '../ConditionObjects.vue';
import DynamicItemInput from '../DynamicItemInput/DynamicItemInput.vue';
import { useFlowEditorStore } from '../../stores/flowEditor';
import { IActionFlow, IActionNode, IActionVariable } from '../../types/ActionTypes';
export default defineComponent({
name: 'ConditionObject',
components: {
ShowDialog,
DynamicItemInput,
// ConditionObjects
},
props: {
disabled: {
type: Boolean,
default: false
},
label: {
type: String,
default: undefined
},
config: {
type: Object,
default: () => {
return {
canInput: false,
buttonsConfig: [
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
{ label: '変数', color: 'green', type: 'VariableAdd' },
]
};
}
},
modelValue: {
type: Object,
default: null
},
},
setup(props, { emit }) {
// const appDg = ref();
const show = ref(false);
const selectedObject = ref(props.modelValue);
const store = useFlowEditorStore();
// const sharedText = ref(''); // 共享的文本状态
const isSelected = computed(() => {
return selectedObject.value?.sharedText !== '';
});
// const isSelected = computed(()=>{
// return selectedObject.value!==null && typeof selectedObject.value === 'object' && ('name' in selectedObject.value)
// });
let vars: IActionVariable[] = [];
if (store.currentFlow !== undefined && store.activeNode !== undefined) {
vars = store.currentFlow.getVarNames(store.activeNode);
}
// const filter=ref('');
const showDg = () => {
show.value = true;
};
const closeDg = (val: string) => {
if (val == 'OK') {
// selectedObject.value = appDg.value.selected[0];
}
};
watchEffect(() => {
emit('update:modelValue', selectedObject.value);
});
return {
store,
// appDg,
show,
showDg,
closeDg,
selectedObject,
vars: reactive(vars),
isSelected,
buttonsConfig: [
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
{ label: '変数', color: 'green', type: 'VariableAdd' },
]
// filter
};
}
});
</script>
<style lang="scss">
.condition-object {
min-width: 200px;
max-height: 40px;
margin: 0 2px;
}
.selected-obj {
margin: 0 2px;
}
</style>

View File

@@ -0,0 +1,271 @@
<template>
<!-- <q-toolbar class="bg-grey-3" flat dense round icon="menu" aria-label="Menu" @click.stop>
<q-toolbar-title>条件エディタ</q-toolbar-title>
<q-space></q-space>
<q-btn flat round dense icon="info" color="blue" @click="showingCondition=!showingCondition"></q-btn>
</q-toolbar> -->
<div class="q-pa-md">
<q-tree :nodes="[tree.root]" node-key="index" children-key="children"
tick-strategy="strict" v-model:ticked="ticked" :expanded="expanded" default-expand-all dense color="primary" >
<template v-slot:header-root="prop">
<!-- root -->
<div class="row items-center" @click.stop>
<q-select v-model="prop.node.logicalOperator" :options="logicalOperators" filled outlined dense></q-select>
<q-btn flat round dense icon="more_horiz" size="sm" >
<q-menu auto-close anchor="top right">
<q-list>
<q-item clickable @click="addGroup(prop.node, LogicalOperator.AND)">
<q-item-section avatar><q-icon name="playlist_add" ></q-icon></q-item-section>
<q-item-section>グループの追加</q-item-section>
</q-item>
<q-item clickable @click="addCondition(prop.node)">
<q-item-section avatar><q-icon name="add_circle_outline" ></q-icon></q-item-section>
<q-item-section >条件式の追加</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</div>
</template>
<template v-slot:header-generic="prop">
<!-- logic group -->
<div v-if="prop.node.type !== NodeType.Condition" class="row items-center" @click.stop>
<q-select v-model="prop.node.logicalOperator" :options="logicalOperators" :outlined="true" :filled="true" :dense="true"></q-select>
<q-btn flat round dense icon="more_horiz" size="sm" >
<q-menu auto-close anchor="top right">
<q-list>
<q-item clickable @click="moveUp(prop.node)">
<q-item-section avatar><q-icon name="arrow_upward" ></q-icon></q-item-section>
<q-item-section >一つ上に移動</q-item-section>
</q-item>
<q-item clickable @click="moveDown(prop.node)">
<q-item-section avatar><q-icon name="arrow_downward" ></q-icon></q-item-section>
<q-item-section >一つ下に移動</q-item-section>
</q-item>
<q-separator inset/>
<q-item clickable @click="addGroup(prop.node, LogicalOperator.AND)">
<q-item-section avatar><q-icon name="playlist_add" ></q-icon></q-item-section>
<q-item-section >グループ追加</q-item-section>
</q-item>
<q-item clickable @click="addCondition(prop.node)">
<q-item-section avatar><q-icon name="add_circle_outline" ></q-icon></q-item-section>
<q-item-section >条件式追加</q-item-section>
</q-item>
<q-separator inset/>
<q-item clickable @click="splitGroup(prop.node)">
<q-item-section avatar><q-icon name="playlist_remove" color="negative"></q-icon></q-item-section>
<q-item-section >グループ化解除</q-item-section>
</q-item>
<q-item clickable @click="removeNode(prop.node)">
<q-item-section avatar><q-icon name="delete" color="negative"></q-icon></q-item-section>
<q-item-section >削除</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</div>
<!-- condition -->
<div @click.stop @keypress.stop v-else >
<div class="row no-wrap items-center q-my-xs">
<ConditionObject v-bind="prop.node" v-model="prop.node.object" :config="leftDynamicItemConfig" class="col-4"/>
<q-select v-model="prop.node.operator" :options="operators" class="operator" :outlined="true" :dense="true"></q-select>
<ConditionObject v-bind="prop.node" v-model="prop.node.value" :config="rightDynamicItemConfig" class="col-4"/>
<!-- <ConditionObject v-bind="prop.node" v-model="prop.node.object" class="col-4"/> -->
<!-- <q-input v-if="!prop.node.object || !('options' in prop.node.object)"
v-model="prop.node.value"
class="condition-value" :outlined="true" :dense="true" ></q-input> -->
<q-select v-if="prop.node.object && ('options' in prop.node.object)"
v-model="prop.node.value"
:options="objectValueOptions(prop.node.object.options)"
clearable
value-key="index"
class="condition-value" :outlined="true" :dense="true" ></q-select>
<q-btn flat round dense icon="more_horiz" size="sm" >
<q-menu auto-close anchor="top right">
<q-list>
<q-item clickable @click="moveUp(prop.node)">
<q-item-section avatar><q-icon name="arrow_upward" ></q-icon></q-item-section>
<q-item-section >一つ上に移動</q-item-section>
</q-item>
<q-item clickable @click="moveDown(prop.node)">
<q-item-section avatar><q-icon name="arrow_downward" ></q-icon></q-item-section>
<q-item-section >一つ下に移動</q-item-section>
</q-item>
<q-separator inset/>
<q-item clickable @click="groupMerge(prop.node)" v-if="canMerge(prop.node)">
<q-item-section avatar><q-icon name="playlist_add"></q-icon></q-item-section>
<q-item-section >グループ化</q-item-section>
</q-item>
<q-separator inset/>
<q-item clickable @click="removeNode(prop.node)">
<q-item-section avatar><q-icon name="delete" color="negative"></q-icon></q-item-section>
<q-item-section>削除</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</div>
</div>
</template>
</q-tree>
<q-tooltip anchor="center middle" v-model="showingCondition" no-parent-event>
import { finished } from 'stream';
{{ conditionString }}
</q-tooltip>
</div>
</template>
<script lang="ts">
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( {
name: 'NodeCondition',
components: {
ConditionObject
},
props:{
conditionTree: {
type: ConditionTree,
default: null
},
show:{
type:Boolean,
default:false
}
},
setup(props) {
const ticked= ref([]);
const showingCondition=ref(false);
const logicalOperators = computed(()=>{
const opts=[];
for(const op in LogicalOperator){
opts.push(LogicalOperator[op as keyof typeof LogicalOperator]);
}
return opts;
});
const operator = inject('Operator')
const operators =computed(()=>{
return operator ? operator : Object.values(Operator);
});
const tree = reactive(props.conditionTree);
const conditionString = computed(()=>{
return tree.buildConditionString(tree.root);
});
const objectValueOptions=(options:any):any[]=>{
const opts:any[] =[];
Object.keys(options).forEach((key) =>
{
const opt=options[key];
opts.push(opt);
});
return opts;
};
const addGroup = (parent:GroupNode, logicOp:LogicalOperator) => {
if(!parent){
parent=tree.root;
}
tree.addNode(parent,new GroupNode(logicOp,parent));
};
const addCondition = (parent:GroupNode) => {
const newNode = new ConditionNode({},Operator.Equal,'',parent);
tree.addNode(parent,newNode);
};
const removeNode = (node:INode) => {
tree.removeNode(node);
};
const moveUp =(node:INode)=>{
tree.moveNode(node,'up');
}
const moveDown =(node:INode)=>{
tree.moveNode(node,'down');
}
const getConditionJson=()=>{
return tree.toJson();
}
//JsonからConditionTreeのインスタンスを作成
const LoadCondition=()=>{
tree.fromJson(conditionString.value);
}
//グループ化
const groupMerge=(node:INode)=>{
const checkedNodes:INode[]=[];
const checkedIndexs:number[] = ticked.value;
checkedIndexs.forEach(index => {
const node = tree.findByIndex(index);
if(node){
checkedNodes.push(node);
}
});
tree.createGroupNode(node,checkedNodes,LogicalOperator.AND);
ticked.value=[];
}
//グループ化可能かをチェックする
const canMerge =(node:INode)=>{
const checkedIndexs:number[] = ticked.value;
const findNode = checkedIndexs.find(index=>node.index===index);
console.log("findNode=>",findNode!==undefined,findNode);
return findNode!==undefined;
}
//グループ化解散
const splitGroup=(node:INode)=>{
tree.dissolveGroupNode(node as GroupNode);
ticked.value=[];
}
const expanded=computed(()=>tree.getGroups(tree.root));
// addCondition(tree.root);
return {
leftDynamicItemConfig :inject('leftDynamicItemConfig'),
rightDynamicItemConfig:inject('rightDynamicItemConfig'),
showingCondition,
conditionString,
tree,
ticked,
logicalOperators,
operators,
addGroup,
addCondition,
removeNode,
moveUp,
moveDown,
LogicalOperator,
Operator,
NodeType,
getConditionJson,
LoadCondition,
objectValueOptions,
expanded,
canMerge,
groupMerge,
splitGroup
};
},
});
</script>
<style lang="scss">
.condition-value{
min-width: 200px;
max-height: 40px;
margin: 0 2px;
}
.operator{
min-width: 150px;
max-height: 40px;
margin-left: 12px;
text-align: center;
font-size: 12pt;
}
</style>

View File

@@ -0,0 +1,63 @@
<template>
<div class="q-gutter-y-md" style="max-width: 600px;">
<q-card >
<q-tabs
v-model="tab"
dense
class="text-grey"
active-color="white"
active-bg-color="primary"
indicator-color="primary"
align="justify"
narrow-indicator
>
<q-tab name="fields" label="フィールド"></q-tab>
<q-tab name="vars" label="変数"></q-tab>
</q-tabs>
<q-separator></q-separator>
<q-tab-panels v-model="tab" animated>
<q-tab-panel name="fields">
<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" >
<variable-list v-model="selected" type="single" :vars="vars"></variable-list>
</q-tab-panel>
</q-tab-panels>
</q-card>
</div>
</template>
<script lang="ts">
import { ref, onMounted, reactive, inject } from 'vue'
import FieldList from './FieldList.vue';
import VariableList from './VariableList.vue';
export default {
name: 'ConditionObjects',
components:{
FieldList,
VariableList
},
props: {
name: String,
type: String,
appId: Number,
vars: Array,
filter:String
},
setup(props) {
const selected = ref([]);
console.log(selected);
return {
sourceFields : inject('sourceFields'),
sourceApp : inject('sourceApp'),
tab: ref('fields'),
selected
}
},
}
</script>

View File

@@ -4,6 +4,7 @@
style="max-width: 400px"
:url="uploadUrl"
:label="title"
:headers="headers"
accept=".csv,.xlsx"
v-on:rejected="onRejected"
v-on:uploaded="onUploadFinished"
@@ -15,7 +16,10 @@
</template>
<script setup lang="ts">
import { createUploaderComponent, useQuasar } from 'quasar';
import { useAuthStore } from 'src/stores/useAuthStore';
import { ref } from 'vue';
const $q=useQuasar();
const authStore = useAuthStore();
const emit =defineEmits(['uploaded']);
/**
* ファイルアップロードを拒否する時の処理
@@ -67,9 +71,12 @@
title: string;
uploadUrl:string;
}
const headers = ref([{name:"Authorization",value:'Bearer ' + authStore.token}]);
const props = withDefaults(defineProps<Props>(), {
title:"設計書から導入する(csv or excel)",
uploadUrl: `${process.env.KAB_BACKEND_URL}createappfromexcel`
uploadUrl: `${process.env.KAB_BACKEND_URL}api/v1/createappfromexcel?format=1`
});
</script>
<style lang="scss">

View File

@@ -8,24 +8,25 @@ import { ref,onMounted,reactive } from 'vue'
import { api } from 'boot/axios';
export default {
name: 'AppSelect',
name: 'DomainSelect',
props: {
name: String,
type: String
},
setup() {
const columns = [
{ name: 'id', required: true,label: 'アプリID',align: 'left',field: 'id',sortable: true},
{ name: 'name', align: 'center', label: 'アプリ名', field: 'name', sortable: true },
{ name: 'creator', label: '作成者', field: 'creator', sortable: true },
{ name: 'createdate', label: '作成日時', field: 'createdate' }
{ name: 'id'},
{ name: 'tenantid', required: true,label: 'テナント',align: 'left',field: 'tenantid',sortable: true},
{ name: 'name', align: 'center', label: 'ドメイン', field: 'name', sortable: true },
{ name: 'url', label: 'URL', field: 'url', sortable: true },
{ name: 'kintoneuser', label: 'アカウント', field: 'kintoneuser' }
]
const rows = reactive([])
onMounted( () => {
api.get('allapps').then(res =>{
res.data.apps.forEach((item) =>
api.get(`api/domains/1`).then(res =>{
res.data.forEach((item) =>
{
rows.push({id:item.appId,name:item.name,creator:item.creator.name,createdate:item.createdAt});
rows.push({id:item.id,tenantid:item.tenantid,name:item.name,url:item.url,kintoneuser:item.kintoneuser});
}
)
});

View File

@@ -0,0 +1,43 @@
<template>
<q-btn-dropdown
color="primay"
push
flat
no-caps
icon="share"
size="md"
:label="userStore.currentDomain.domainName"
>
<q-list>
<q-item v-for="domain in domains" :key="domain.domainName"
clickable v-close-popup @click="onItemClick(domain)">
<q-item-section side>
<q-icon name="share" size="sm" color="orange" text-color="white"></q-icon>
</q-item-section>
<q-item-section>
<q-item-label>{{domain.domainName}}</q-item-label>
<q-item-label caption>{{domain.kintoneUrl}}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</template>
<script setup lang="ts" >
import { IDomainInfo } from 'src/types/ActionTypes';
import { useAuthStore,IUserState } from 'stores/useAuthStore';
import { ref } from 'vue';
const userStore = useAuthStore();
const domains = ref<IDomainInfo[]>([]);
(async ()=>{
domains.value = await userStore.getUserDomains();
})();
const onItemClick=(domain:IDomainInfo)=>{
console.log(domain);
userStore.setCurrentDomain(domain);
}
</script>
<style lang="scss">
</style>

View File

@@ -0,0 +1,141 @@
<template>
<div class="q-mx-md" style="max-width: 600px;">
<!-- <q-card> -->
<div class="q-mb-md">
<q-input ref="inputRef" outlined dense debounce="200" @update:model-value="updateSharedText"
v-model="sharedText" :readonly="!canInput" autogrow>
<template v-slot:append>
<q-btn flat round padding="none" icon="cancel" @click="clearSharedText" color="grey-6" />
</template>
</q-input>
</div>
<div class="row q-gutter-sm">
<q-btn v-for="button in buttonsConfig" :key="button.type" :color="button.color" @mousedown.prevent
@click="openDialog(button)" size="sm">
{{ button.label }}
</q-btn>
</div>
<show-dialog v-model:visible="dialogVisible" :name="currentDialogName" @close="closeDialog" min-width="400px">
<template v-slot:toolbar>
<q-input dense debounce="200" v-model="filter" clearable>
<template v-slot:before>
<q-icon name="search" />
</template>
</q-input>
</template>
<!-- asdf -->
<component :is="currentComponent" @select="handleSelect" :filter="filter" :appId="appId" />
</show-dialog>
<!-- </q-card> -->
</div>
</template>
<script lang="ts">
import { ref, inject, watchEffect, defineComponent } from 'vue';
import FieldAdd from './FieldAdd.vue';
import VariableAdd from './VariableAdd.vue';
// import FunctionAdd from './FunctionAdd.vue';
import ShowDialog from '../ShowDialog.vue';
type ButtonConfig = {
label: string;
color: string;
type: string;
editable: boolean;
};
export default defineComponent({
name: 'DynamicItemInput',
components: {
FieldAdd,
VariableAdd,
// FunctionAdd,
ShowDialog
},
props: {
// canInput: {
// type: Boolean,
// default: false
// },
appId: {
type: String,
},
selectedObject: {
default: {}
},
buttonsConfig: {
type: Array as () => ButtonConfig[],
default: () => [
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' }
]
}
},
setup(props, { emit }) {
const filter = ref('');
const dialogVisible = ref(false);
const currentDialogName = ref('');
const currentComponent = ref('FieldAdd');
const sharedText = ref(props.selectedObject?.sharedText ?? '');
const inputRef = ref();
const canInput = ref(true);
const editable = ref(false);
const openDialog = (button: ButtonConfig) => {
currentDialogName.value = button.label;
currentComponent.value = button.type;
dialogVisible.value = true;
editable.value = button.editable ?? true;
};
const closeDialog = () => {
dialogVisible.value = false;
};
const handleSelect = (value:any) => {
// 获取当前光标位置
// const cursorPosition = inputRef.value.getNativeElement().selectionStart;
// if (cursorPosition === undefined || cursorPosition === 0) {
sharedText.value = `${value._t}`;
// } else {
// const textBefore = sharedText.value.substring(0, cursorPosition);
// const textAfter = sharedText.value.substring(cursorPosition);
// sharedText.value = `${textBefore}${value._t}${textAfter}`;
// }
if (value && value._t && (value._t as string).length > 0) {
canInput.value = editable.value;
}
emit('update:selectedObject', { sharedText: sharedText.value, ...value });
dialogVisible.value = false;
};
const clearSharedText = () => {
sharedText.value = '';
canInput.value = true;
emit('update:selectedObject', {});
}
const updateSharedText = (value:string) => {
sharedText.value = value;
emit('update:selectedObject', { ...props.selectedObject, sharedText: value,objectType:'text' });
}
return {
filter,
dialogVisible,
currentDialogName,
currentComponent,
canInput,
openDialog,
closeDialog,
handleSelect,
clearSharedText,
updateSharedText,
sharedText,
inputRef
};
}
});
</script>

View File

@@ -0,0 +1,41 @@
<template>
<field-list v-model="selected" type="single" :filter="filter" :appId="sourceApp ? sourceApp : appId"
:fields="sourceFields" @update:modelValue="handleSelect" />
</template>
<script lang="ts">
import { computed, inject, ref } from 'vue';
import FieldList from '../FieldList.vue';
export default {
name: 'FieldAdd',
components: {
FieldList,
},
props: {
appId: Number,
filter: String
},
setup(props, { emit }) {
const sourceFields = inject<Array<unknown>>('sourceFields')
const sourceApp = inject<number>('sourceApp')
const appId = computed(() => {
if (sourceFields || sourceApp) {
return sourceApp.value
} else {
return props.appId
}
});
return {
sourceFields,
sourceApp,
selected: ref([]),
handleSelect: (newSelection: any[]) => {
if (newSelection.length > 0) {
const v = newSelection[0]
emit('select', { _t: `field(${appId.value},${v.name})`, ...v }); // 假设您只需要选择的第一个字段的名称
}
}
}
},
}
</script>

View File

@@ -0,0 +1,42 @@
<template>
<variable-list v-model="selected" type="single" :vars="vars" :filter="filter" @update:modelValue="handleSelect" />
</template>
<script lang="ts">
import { ref } from 'vue';
import VariableList from '../VariableList.vue';
import { useFlowEditorStore } from 'src/stores/flowEditor';
import { IActionVariable } from 'src/types/ActionTypes';
export default {
name: 'VariableAdd',
components: {
VariableList,
},
props: {
appId: Number,
filter: String
},
setup(props, { emit }) {
const store = useFlowEditorStore();
let vars: IActionVariable[] = [];
console.log(store.currentFlow !== undefined && store.activeNode !== undefined);
if (store.currentFlow !== undefined && store.activeNode !== undefined) {
vars = store.currentFlow.getVarNames(store.activeNode);
}
return {
vars,
selected: ref([]),
handleSelect: (newSelection: any[]) => {
if (newSelection.length > 0) {
const v = newSelection[0];
let name = v.name
if (typeof name === 'object') {
name = name.name
}
emit('select', { _t: `var(${name})`, ...v }); // 假设您只需要选择的第一个字段的名称
}
}
}
},
}
</script>

View File

@@ -0,0 +1,58 @@
<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" />
</div>
</template>
<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'
],
setup(props) {
// const rows = ref([]);
// const isLoaded = ref(false);
const columns = [
{ 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 { state : rows, isReady: isLoaded, isLoading } = useAsyncState((args) => {
if (props.fields && Object.keys(props.fields).length > 0) {
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,
// selected: ref([]),
isLoaded
}
},
}
</script>

View File

@@ -1,45 +1,92 @@
<template>
<div class="q-pa-md">
<q-table :title="name+'一覧'" row-key="name" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows" />
<div class="q-px-md" style=" min-width: 50vw; max-width: 85vw;">
<div v-if="!isLoaded" class="spinner flex flex-center">
<q-spinner color="primary" size="3em" />
</div>
<q-table flat bordered v-else row-key="name" :selection="type" v-model:selected="selected" :columns="columns"
:rows="rows" :pagination="pageSetting" :filter="filter" style="max-height: 55vh;"/>
</div>
</template>
<script>
import { ref,onMounted,reactive } from 'vue'
import { ref, onMounted, reactive, watchEffect } from 'vue'
import { api } from 'boot/axios';
export default {
name: 'fieldSelect',
props: {
name: String,
type: String,
appId:Number
type: {
type: String,
default: 'single'
},
appId: Number,
not_page: {
type: Boolean,
default: false,
},
selectedFields:{
type:Array,
default:()=>[]
},
fieldTypes:{
type:Array,
default:()=>[]
},
filter: String,
updateSelectFields: {
type: Function
},
},
setup(props) {
const columns = [
{ name: 'name', required: true,label: 'フィールド名',align: 'left',field: row=>row.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( () => {
api.get('appfields', {
params:{
app: props.appId
}
}).then(res =>{
let fields = res.data.properties;
console.log(fields);
Object.keys(fields).forEach((key) =>
{
rows.push({name:fields[key].label,code:fields[key].code,type:fields[key].type});
const isLoaded = ref(false);
const columns = [
{ name: 'name', required: true, label: 'フィールド', align: 'left', field: row => row.name, sortable: true },
{ name: 'code', label: 'フィールドコード', align: 'left', field: 'code', sortable: true },
{ name: 'type', label: 'フィールドタイプ', align: 'left', field: 'type', sortable: true }
]
const pageSetting = ref({
sortBy: 'desc',
descending: false,
page: 1,
rowsPerPage: props.not_page ? 0 : 5
// rowsNumber: xx if getting data from a server
});
const rows = reactive([]);
const selected = ref(props.selectedFields && props.selectedFields.length>0?props.selectedFields:[]);
onMounted(async () => {
const url = props.fieldTypes.includes('SPACER')?'api/v1/allfields':'api/v1/appfields';
const res = await api.get(url, {
params: {
app: props.appId
}
)
});
let fields = res.data.properties;
Object.keys(fields).forEach((key) => {
const fld = fields[key];
if(props.fieldTypes.length===0 || props.fieldTypes.includes(fld.type)){
rows.push({ name: fld.label || fld.code, ...fld });
}else if(props.fieldTypes.includes("lookup") && ("lookup" in fld)){
rows.push({ name: fld.label || fld.code, ...fld });
}
});
isLoaded.value = true;
});
watchEffect(()=>{
if (selected.value && selected.value[0] && props.updateSelectFields) {
props.updateSelectFields(selected)
}
});
return {
columns,
rows,
selected: ref([]),
selected,
isLoaded,
pageSetting
}
},

View File

@@ -1,12 +1,14 @@
<template>
<div class="q-pa-md q-gutter-sm">
<q-dialog :model-value="visible" persistent>
<q-card style="min-width: 350px">
<q-card-section>
<div class="text-h6">{{ name }}選択</div>
</q-card-section>
<q-card-section class="q-pt-none">
<!-- <div class="q-pa-md q-gutter-sm" > -->
<q-dialog :model-value="visible" persistent bordered >
<q-card class="" 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>
<slot name="toolbar"></slot>
<q-btn flat round dense icon="close" @click="CloseDialogue('Cancel')" />
</q-toolbar>
<q-card-section class="q-mt-md" :style="sectionStyle">
<slot></slot>
</q-card-section>
<q-card-actions align="right" class="text-primary">
@@ -15,15 +17,19 @@
</q-card-actions>
</q-card>
</q-dialog>
</div>
<!-- </div> -->
</template>
<script>
import {computed} from 'vue'
export default {
name: 'ShowDialog',
props: {
name:String,
visible: Boolean,
width:String,
height:String,
minWidth:String,
minHeight:String
},
emits: [
'close'
@@ -34,8 +40,20 @@ export default {
context.emit('close', val);
}
const cardStyle = computed(() => ({
minWidth: props.minWidth,
width: props.width
}));
const sectionStyle = computed(() => ({
height: props.height,
minHeight: props.minHeight
}));
return {
CloseDialogue
CloseDialogue,
cardStyle,
sectionStyle
}
},
}

View File

@@ -0,0 +1,61 @@
<template>
<div class="q-pa-md">
<q-table flat bordered row-key="id" :selection="type" :selected="modelValue" :filter="filter"
@update:selected="$emit('update:modelValue', $event)" :columns="columns" :rows="rows" />
</div>
</template>
<script lang="ts">
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: () => []
},
modelValue: Array,
filter: String
},
emits: [
'update:modelValue'
],
setup(props) {
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.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)
}
}
}
</script>

View File

@@ -1,11 +1,5 @@
<template>
<div
class="row"
style="
border-radius: 2px;
box-shadow: rgba(255, 255, 255, 0.1) 0px 0px 0px 1px inset,
rgba(0, 0, 0, 0.3) 0px 0px 0px 1px;
">
<div class="row app-box">
<q-icon
class="self-center q-ma-sm"
name="widgets"
@@ -13,7 +7,9 @@
style="font-size: 2em"
/>
<div class="col-7 self-center ellipsis">
{{ selectedApp.name }}
<a :href="!store.appInfo?'':`${authStore.currentDomain.kintoneUrl}/k/${store.appInfo?.appId}`" target="_blank" title="Kiontoneへ">
{{ store.appInfo?.name }}
</a>
</div>
<div class="self-center">
<q-btn
@@ -26,8 +22,15 @@
></q-btn>
</div>
</div>
<ShowDialog v-model:visible="showSelectApp" name="アプリ" @close="closeDg">
<AppSelect ref="appDg" name="アプリ" type="single"></AppSelect>
<ShowDialog v-model:visible="showSelectApp" 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>
@@ -35,38 +38,37 @@
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({
name: 'AppSelector',
emits:[
"appSelected"
],
components:{
AppSelect,
AppSelectBox,
ShowDialog
},
setup(props, context) {
const store = useFlowEditorStore();
const authStore=useAuthStore();
const appDg = ref();
const showSelectApp=ref(false);
const selectedApp =ref<AppInfo>({
appId:"",
name:"",
});
const closeDg=(val :any)=>{
showSelectApp.value=false;
console.log("Dialog closed->",val);
if (val == 'OK') {
const data = appDg.value.selected[0];
console.log(data);
selectedApp.value={
const appInfo={
appId:data.id ,
name:data.name
};
store.setApp(selectedApp.value);
store.setFlow();
store.setApp(appInfo);
store.loadFlow();
}
}
const showAppDialog=()=>{
@@ -74,12 +76,19 @@ export default defineComponent({
}
return {
store,
selectedApp,
authStore,
showSelectApp,
showAppDialog,
closeDg,
appDg
appDg,
filter:ref('')
}
}
});
</script>
<style lang="scss">
.app-box{
border-radius: 2px;
box-shadow: rgba(255, 255, 255, 0.1) 0px 0px 0px 1px inset,rgba(0, 0, 0, 0.3) 0px 0px 0px 1px;
}
</style>

View File

@@ -1,89 +1,178 @@
<template>
<div class="q-pa-md q-gutter-sm">
<q-tree
:nodes="store.eventTree.screens"
node-key="label"
children-key="events"
no-connectors
v-model:expanded="expanded"
:dense="true"
>
<template v-slot:default-header="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>
</div>
</template>
</q-tree>
</div>
<!-- <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 :ref="prop.node.eventId" class="row col items-center 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> -->
</div>
</template>
<template v-slot:header-CHANGE="prop">
<div class="row col items-start no-wrap event-node">
<div class="no-wrap"
:class="selectedEvent && prop.node.eventId === selectedEvent.eventId ? 'selected-node' : ''"
>{{ 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-start 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" />
<div class="no-wrap" :class="selectedEvent && prop.node.eventId === selectedEvent.eventId ? 'selected-node' : ''" >{{ prop.node.label }}</div>
<q-space></q-space>
<q-icon name="delete_forever" color="negative" size="16px" @click="deleteEvent(prop.node)"></q-icon>
</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 } 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 { S } from 'app/dist/spa/assets/QTable.50486f7c';
import { defineComponent, ref } from 'vue';
import { IKintoneEvent, IKintoneEventGroup, IKintoneEventNode } from '../../types/KintoneEvents';
import FieldSelect from '../FieldSelect.vue';
import ShowDialog from '../ShowDialog.vue';
export default defineComponent({
name: 'EventTree',
components: {
ShowDialog,
FieldSelect,
},
setup(props, context) {
const $q = useQuasar();
const appDg = ref();
const store = useFlowEditorStore();
const showDialog = ref(false);
const tree = ref<QTree>();
// const eventTree=ref(kintoneEvents);
// const selectedFlow = store.currentFlow;
const expanded=ref([
store.currentFlow?.getRoot()?.title
]);
const selectedEvent = ref<IKintoneEvent|null>(null);
const onSelected=(node:IKintoneEvent)=>{
if(!node.eventId){
return;
}
selectedEvent.value=node;
if(store.appInfo===undefined){
return;
}
const screen = store.eventTree.findScreen(node.eventId);
let flow =store.findFlowByEventId(node.eventId);
const screenName=screen!==null?screen.label:"";
if(flow!==undefined && flow!==null ){
store.selectFlow(flow);
}else{
const root = new RootAction(node.eventId,screenName,node.label)
const flow =new ActionFlow(root);
store.flows?.push(flow);
store.selectFlow(flow);
selectedEvent.value.flowData=flow;
}
// 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 closeDg = (val: string) => {
if (val == 'OK') {
if (!selectedChangeEvent.value) { return; }
const field = appDg.value.selected[0];
const eventid = `${selectedChangeEvent.value.eventId}.${field.code}`;
if (store.eventTree.findEventById(eventid)) {
return;
}
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;
}
selectedChangeEvent.value = node;
showDialog.value = true;
}
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,
expanded,
// expanded,
appDg,
tree,
showDialog,
isFieldChange,
onSelected,
selectedEvent,
addChangeEvent,
deleteEvent,
closeDg,
store
}
}
});
</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

@@ -1,5 +1,5 @@
<template>
<div class="row justify-center" :style="{ marginLeft: node.inputPoint !== '' ? '240px' : '' }" >
<div class="row justify-center no-wrap" >
<div class="row">
<q-card class="action-node" :class="nodeStyle" :square="false" @click="onNodeClick" >
<q-toolbar class="col" >
@@ -8,6 +8,10 @@
<q-btn flat round dense icon="more_horiz" size="sm" >
<q-menu auto-close anchor="top right">
<q-list>
<q-item clickable v-if="isRoot" @click="copyFlow">
<q-item-section avatar><q-icon name="content_copy" ></q-icon></q-item-section>
<q-item-section >コピーする</q-item-section>
</q-item>
<q-item clickable v-if="!isRoot" @click="onEditNode">
<q-item-section avatar><q-icon name="edit" ></q-icon></q-item-section>
<q-item-section >編集する</q-item-section>
@@ -25,8 +29,12 @@
</q-btn>
</q-toolbar>
<q-separator />
<q-card-section>
<div class="text-h7">{{ node.title }}</div>
<q-card-section class="action-title">
<div class="row">
<span class="text-h7">{{ node.title }}</span>
<q-space></q-space>
<q-chip color="info" text-color="white" size="0.70rem" v-if="varName(node)" clickable>{{ varName(node) }}</q-chip>
</div>
</q-card-section>
<template v-if="hasBranch">
<q-separator />
@@ -40,23 +48,34 @@
</div>
</div>
<template v-if="hasBranch">
<div class="row justify-center" :style="{ marginLeft: node.inputPoint !== '' ? '240px' : '' }">
<div v-for="(point, index) in node.outputPoints" :key="index">
<node-line :action-node="node" :mode="getMode(point)" @addNode="addNode" :input-point="point"></node-line>
<node-line :action-node="node" @addNode="addNode" :left-columns="leftColumns" :right-columns="rightColumns"></node-line>
<div class="row justify-center no-wrap" >
<div v-for="(point, index) in node.outputPoints" :key="index" class="column" style="min-width: 300px;">
<div class="justify-center" >
<node-item v-if="nextNode(point)!==undefined" :key="nextNode(point).id" :isSelected="nextNode(point) === store.activeNode"
:actionNode="nextNode(point)" @addNode="addNodeFromItem" @nodeSelected="onNodeSelected" @nodeEdit="onNodeEdit"
@deleteNode="onDeleteNodeFromItem" @deleteAllNextNodes="onDeleteAllNextNodes" ></node-item>
</div>
</div>
</div>
</template>
<template v-if="!hasBranch">
<div class="row justify-center" :style="{ marginLeft: node.inputPoint !== '' ? '240px' : '' }">
<node-line :action-node="node" :mode="getMode('')" @addNode="addNode" input-point=""></node-line>
<div class="row justify-center no-wrap" >
<node-line :action-node="node" @addNode="addNode" ></node-line>
</div>
<div>
<node-item v-if="nextNode('')!==undefined" :key="nextNode('').id" :isSelected="nextNode('') === store.activeNode"
:actionNode="nextNode('')" @addNode="addNodeFromItem" @nodeSelected="onNodeSelected" @nodeEdit="onNodeEdit"
@deleteNode="onDeleteNodeFromItem" @deleteAllNextNodes="onDeleteAllNextNodes" ></node-item>
</div>
</template>
</template>
<script lang="ts">
import { defineComponent, computed, ref } from 'vue';
import { IActionNode } from '../../types/ActionTypes';
import { IActionNode, IActionProperty } from '../../types/ActionTypes';
import NodeLine, { Direction } from '../main/NodeLine.vue';
import { useFlowEditorStore } from 'stores/flowEditor';
export default defineComponent({
name: 'NodeItem',
components: {
@@ -77,8 +96,10 @@ export default defineComponent({
"nodeEdit",
"deleteNode",
"deleteAllNextNodes",
"copyFlow"
],
setup(props, context) {
const store = useFlowEditorStore();
const hasBranch = computed(() => props.actionNode.outputPoints.length > 0);
const nodeStyle = computed(() => {
return {
@@ -87,23 +108,11 @@ export default defineComponent({
'selected': props.isSelected && !props.actionNode.isRoot
};
});
const getMode = (point: string) => {
if (point === '' || props.actionNode.outputPoints.length === 0) {
return Direction.Default;
}
if (point === props.actionNode.outputPoints[0]) {
if (props.actionNode.nextNodeIds.get(point)) {
return Direction.Left;
} else {
return Direction.LeftNotNext;
}
} else {
if (props.actionNode.nextNodeIds.get(point)) {
return Direction.Right;
} else {
return Direction.RightNotNext;
}
}
const nextNode=(point:string)=>{
const nextId= props.actionNode.nextNodeIds.get(point);
if(!nextId) return undefined;
return store.currentFlow?.findNodeById(nextId);
}
/**
* アクションノード追加イベントを
@@ -112,6 +121,38 @@ export default defineComponent({
const addNode = (point: string) => {
context.emit('addNode', props.actionNode, point);
}
/**
* アクションノード追加イベントを
* @param point 入力ポイント
*/
const addNodeFromItem = (node:IActionNode,point: string) => {
context.emit('addNode', node, point);
}
const leftColumns=computed(()=>{
if(!props.actionNode.outputPoints || props.actionNode.outputPoints.length<2){
return 1;
}
const leftNode = nextNode(props.actionNode.outputPoints[0]);
if(leftNode){
return store.currentFlow?.getColumns(leftNode);
}else{
return 1;
}
});
const rightColumns=computed(()=>{
if(!props.actionNode.outputPoints || props.actionNode.outputPoints.length<2){
return 1;
}
const rightNode = nextNode(props.actionNode.outputPoints[1]);
if(rightNode){
return store.currentFlow?.getColumns(rightNode);
}else{
return 1;
}
});
/**
* ノード選択状態
*/
@@ -119,9 +160,20 @@ export default defineComponent({
context.emit('nodeSelected', props.actionNode);
}
const onNodeSelected = (node: IActionNode) => {
context.emit('nodeSelected', node);
}
const onEditNode=()=>{
context.emit('nodeEdit', props.actionNode);
}
const onNodeEdit=(node:IActionNode)=>{
context.emit('nodeEdit', node);
}
/**
* ノードを削除する
*/
@@ -129,30 +181,68 @@ export default defineComponent({
context.emit('deleteNode', props.actionNode);
}
/**
* ノードを削除する
*/
const onDeleteNodeFromItem=(node:IActionNode)=>{
context.emit('deleteNode', node);
}
/**
* ノードの以下すべて削除する
*/
const onDeleteAllNode=()=>{
context.emit('deleteAllNextNodes', props.actionNode);
};
/**
* ノードの以下すべて削除する
*/
const onDeleteAllNextNodes=(node:IActionNode)=>{
context.emit('deleteAllNextNodes', node);
};
/**
* 変数名取得
*/
const varName =(node:IActionNode)=>{
const prop = node.actionProps.find((prop) => prop.props.name === "verName");
return prop?.props.modelValue.name;
};
const copyFlow=()=>{
context.emit('copyFlow', props.actionNode);
}
return {
store,
node: props.actionNode,
nextNode,
isRoot: props.actionNode.isRoot,
hasBranch,
nodeStyle,
getMode,
// getMode,
addNode,
addNodeFromItem,
onNodeClick,
onNodeSelected,
onEditNode,
onNodeEdit,
onDeleteNode,
onDeleteAllNode
onDeleteNodeFromItem,
onDeleteAllNode,
onDeleteAllNextNodes,
copyFlow,
varName,
leftColumns,
rightColumns
}
}
});
</script>
<style lang="scss">
.action-node {
min-width: 300px !important;
min-width: 280px !important;
}
.action-title{
max-width: 280px !important;
overflow-wrap: anywhere;
}
.line {

View File

@@ -1,11 +1,28 @@
<template>
<div>
<svg class="node-line">
<polyline :points="points.linePoints" class="line" ></polyline>
<text class="add-icon" @click="addNode(node)" :x="points.iconPoint.x" :y="points.iconPoint.y" font-family="Arial" font-size="25"
text-anchor="middle" dy=".3em" style="cursor: pointer;" >
</text>
<div class="row justify-center">
<svg class="node-line" style="width:100%" :viewBox="viewBox()">
<template v-if="!node.outputPoints || node.outputPoints.length===0" >
<polyline :points="points(getMode('')).linePoints" class="line" ></polyline>
<text class="add-icon"
@click="addNode(node,'')"
:x="points(getMode('')).iconPoint.x"
:y="points(getMode('')).iconPoint.y"
font-family="Arial" font-size="25"
text-anchor="middle" dy=".3em" style="cursor: pointer;" >
</text>
</template>
<template v-for="(point, index) in node.outputPoints" :key="index" >
<polyline :points="points(getMode(point)).linePoints" class="line" ></polyline>
<text class="add-icon"
@click="addNode(node,point)"
:x="points(getMode(point)).iconPoint.x"
:y="points(getMode(point)).iconPoint.y"
font-family="Arial" font-size="25"
text-anchor="middle" dy=".3em" style="cursor: pointer;" >
</text>
</template>
</svg>
</div>
</template>
@@ -27,55 +44,97 @@ export default defineComponent({
type: Object as PropType<IActionNode>,
required: true
},
mode: {
type: String as PropType<Direction>,
required: true
leftColumns:{
type:Number,
required:false
},
inputPoint:{
type:String
rightColumns:{
type:Number,
required:false
}
},
emits: ['addNode'],
setup(props,context) {
const hasBranch = computed(() => props.actionNode.outputPoints.length > 0);
const points = computed(() => {
switch (props.mode) {
case Direction.Left:
const getMode = (point: string):Direction => {
if (point === '' || props.actionNode.outputPoints.length === 0) {
return Direction.Default;
}
if (point === props.actionNode.outputPoints[0]) {
if (props.actionNode.nextNodeIds.get(point)) {
return Direction.Left;
} else {
return Direction.LeftNotNext;
}
} else {
if (props.actionNode.nextNodeIds.get(point)) {
return Direction.Right;
} else {
return Direction.RightNotNext;
}
}
}
const points = (mode:Direction) => {
let startX ,endX;
const leftColumn=props.leftColumns?props.leftColumns:1;
const rightColumn=props.rightColumns?props.rightColumns:1;
switch (mode) {
case Direction.Left:
startX = leftColumn*300/2.0;
endX = ((leftColumn+rightColumn)/2.0 - 0.25)*300;
return {
linePoints: '180, 0, 180, 40, 120, 40, 120, 60',
iconPoint: { x: 180, y: 20 }
linePoints: `${startX}, 60, ${startX}, 40, ${endX}, 40, ${endX}, 0`,
iconPoint: { x: endX, y: 20 }
};
case Direction.Right:
startX = ((leftColumn+rightColumn)/2.0 + 0.25)*300;
endX = (leftColumn+(rightColumn/2.0))*300;
return {
linePoints: '60, 0, 60, 40, 120, 40, 120, 60',
iconPoint: { x: 60, y: 20 }
linePoints: `${startX}, 0, ${startX}, 40, ${endX}, 40, ${endX}, 60`,
iconPoint: { x: startX, y: 20 }
};
case Direction.LeftNotNext:
startX = ((leftColumn+rightColumn)/2.0 - 0.25)*300;
return {
linePoints: '180, 0, 180, 40',
iconPoint: { x: 180, y: 20 }
linePoints: `${startX}, 0, ${startX}, 40`,
iconPoint: { x: startX, y: 20 }
};
case Direction.RightNotNext:
startX = ((leftColumn+rightColumn)/2.0 + 0.25)*300;
return {
linePoints: '60, 0, 60, 40',
iconPoint: { x: 60, y: 30 }
linePoints: `${startX}, 0, ${startX}, 40`,
iconPoint: { x: startX, y: 20 }
};
default:
return {
linePoints: '120, 0, 120, 60',
iconPoint: { x: 120, y: 30 }
linePoints: '150, 0, 150, 60',
iconPoint: { x: 150, y: 30 }
};
}
});
const addNode=(prveNode:IActionNode)=>{
context.emit('addNode',props.inputPoint);
};
const addNode=(prveNode:IActionNode,point:string)=>{
context.emit('addNode',point);
}
const viewBox=()=>{
let columns=0;
if(props.leftColumns!==undefined) columns+=props.leftColumns;
if(props.rightColumns!==undefined) columns+=props.rightColumns;
if(columns===0) columns=1;
const width= columns*300;
return `0 0 ${width} 60`;
};
return {
node: props.actionNode,
getMode,
hasBranch,
points,
addNode
addNode,
viewBox
}
}
});

View File

@@ -1,25 +1,27 @@
<template>
<div>
<div v-for="(item, index) in componentData" :key="index">
<component :is="item.component" v-bind="item.props" v-model="item.props.modelValue"></component>
<component :is="item.component" v-bind="item.props" :connectProps="connectProps" v-model="item.props.modelValue"></component>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { defineComponent,computed } from 'vue';
import InputText from '../right/InputText.vue';
import SelectBox from '../right/SelectBox.vue';
import DatePicker from '../right/DatePicker.vue';
import FieldInput from '../right/FieldInput.vue';
import EventSetter from '../right/EventSetter.vue';
import { IActionProperty, IProp } from 'src/types/ActionTypes';
export default defineComponent({
name: 'ActionProperty',
components: {
InputText,
SelectBox,
DatePicker,
FieldInput
FieldInput,
EventSetter
},
props: {
jsonData: {
@@ -31,27 +33,43 @@ export default defineComponent({
required: false,
}
},
computed: {
componentData() {
return this.jsonData.elements.map((element: any) => {
if(this.jsonValue != undefined )
setup(props){
const componentData=computed<Array<IActionProperty>>(()=>{
return props.jsonData.elements.map((element: any) => {
if(props.jsonValue != undefined )
{
if(this.jsonValue.hasOwnProperty(element.props.name))
if(props.jsonValue.hasOwnProperty(element.props.name))
{
element.props.modelValue = this.jsonValue[element.props.name];
element.props.modelValue = props.jsonValue[element.props.name];
}
else
{
element.props.modelValue = '';
}
}
return {
component: element.component,
props: element.props,
};
});
},
},
});
const connectProps=(props:IProp)=>{
const connProps:any={};
if(props && "connectProps" in props && props.connectProps!=undefined){
for(let connProp of props.connectProps){
let targetProp = componentData.value.find((prop)=>prop.props.name===connProp.propName);
if(targetProp){
connProps[connProp.key]=targetProp;
}
}
}
return connProps;
}
return{
componentData,
connectProps
}
}
});
</script>

View File

@@ -0,0 +1,157 @@
<template>
<div class="q-my-md" v-bind="$attrs">
<q-card flat>
<q-card-section class="q-pa-none q-my-sm q-mr-md">
<!-- <div class=" q-my-none ">App Field Select</div> -->
<div class="row q-mb-xs">
<div class="text-primary q-mb-xs text-caption">{{ $props.displayName }}</div>
</div>
<div class="row">
<div class="col">
<div class="q-mb-xs">{{ selectedField.app?.name || '未選択' }}</div>
</div>
<div class="col-1">
<q-btn round flat size="sm" color="primary" icon="search" @click="showDg" />
</div>
</div>
</q-card-section>
<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>
<!-- <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">
<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>
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeAFBox">
<AppFieldSelectBox v-model:selectedField="selectedField" :selectType="selectType" ref="afBox" :fieldTypes="fieldTypes"/>
</show-dialog>
</template>
<script lang="ts">
import { defineComponent, ref, watchEffect } from 'vue';
import AppFieldSelectBox from '../AppFieldSelectBox.vue';
import ShowDialog from '../ShowDialog.vue';
import { useFlowEditorStore } from 'stores/flowEditor';
export interface IApp {
id: string,
name: string
}
export interface IField {
name: string,
code: string,
type: string,
label?:string
}
export interface IAppFields {
app?: IApp,
fields: IField[]
}
export default defineComponent({
inheritAttrs: false,
name: 'AppFieldSelect',
components: {
ShowDialog,
AppFieldSelectBox
},
props: {
displayName: {
type: String,
default: '',
},
name: {
type: String,
default: '',
},
placeholder: {
type: String,
default: '',
},
modelValue: {
type: Object,
default: null
},
selectType: {
type: String,
default: 'single'
},
fieldTypes:{
type:Array,
default:()=>[]
}
},
setup(props, { emit }) {
const show = ref(false);
const afBox = ref();
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 clear = () => {
selectedField.value = {
fields: []
};
}
const removeField = (index: number) => {
selectedField.value.fields.splice(index, 1);
}
const closeAFBox = (val: string) => {
if (val == 'OK') {
console.log(afBox.value);
selectedField.value = afBox.value.selField;
}
};
watchEffect(() => {
emit('update:modelValue', selectedField.value);
});
return {
store,
afBox,
show,
showDg: () => { show.value = true },
selectedField,
clear,
removeField,
closeAFBox,
};
}
});
</script>

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

@@ -0,0 +1,74 @@
<template>
<div class="" v-bind="$attrs">
<q-field v-model="color" :label="displayName" labelColor="primary" :clearable="isSelected" stack-label :bottom-slots="!isSelected" >
<template v-slot:control>
<q-chip text-color="black" color="white" v-if="isSelected">
<div class="row">
<div class="col-4">
<q-avatar class="shadow-1" :style="{ background: color }" size="xs"></q-avatar>
</div>
<div class="col">
{{ color }}
</div>
</div>
</q-chip>
</template>
<template v-slot:append>
<q-icon name="colorize" class="cursor-pointer" color="primary" >
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-color no-header default-view="palette" v-model="color" />
</q-popup-proxy>
</q-icon>
</template>
<template v-slot:hint>
{{ placeholder }}
</template>
</q-field>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, ref,watchEffect } from 'vue';
export default defineComponent({
inheritAttrs:false,
name: 'ColorPicker',
components: {
},
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 color = ref(props.modelValue??"");
const isSelected = computed(()=>props.modelValue && props.modelValue!=="");
watchEffect(()=>{
emit('update:modelValue', color.value);
});
return {
color,
isSelected
};
}
});
</script>

View File

@@ -0,0 +1,194 @@
<template>
<div v-bind="$attrs">
<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" :disable="btnDisable" @click="showDg()">クリックで設定{{ isSetted ?
'設定済み' : '未設定' }}</q-btn>
</q-card-actions>
<q-card-section class="text-caption">
<div v-if="!isSetted">{{ placeholder }}</div>
<div v-else>{{ conditionString }}</div>
</q-card-section>
</q-card>
</template>
</q-field>
<condition-editor v-model:show="show" v-model:conditionTree="tree" @closed="onClosed"></condition-editor>
</div>
</template>
<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;
}[]
}
}
};
type InputConfg = {
canInput: boolean;
buttonsConfig: {
label: string;
color: string;
type: 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,
},
inputConfig: {
type: Object,
default: () => ({
left: {
canInput: false,
buttonsConfig: [
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
{ label: '変数', color: 'green', type: 'VariableAdd' },
]
},
right: {
canInput: true,
buttonsConfig: [
{ label: '変数', color: 'green', type: 'VariableAdd' },
]
},
})
}
},
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('leftDynamicItemConfig', props.inputConfig.left);
provide('rightDynamicItemConfig', props.inputConfig.right);
provide('Operator', props.operatorList);
const btnDisable = computed(() => {
const onlySourceSelect = props.onlySourceSelect;
if (!onlySourceSelect) {
return false;
}
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 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 isSetted = ref(props.modelValue && props.modelValue !== '');
const conditionString = computed(() => {
return tree.buildConditionString(tree.root);
});
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);
}
};
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,284 @@
<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="55vw" min-height="60vh">
<div class="">
<div class="row q-col-gutter-x-xs flex-center">
<div class="col-5">
<div class="q-mx-xs">ソース</div>
</div>
<!-- <div class="col-1">
</div> -->
<div class="col-5">
<div class="row justify-between q-mr-md">
<div class="">{{ sourceApp?.name }}</div>
<q-btn outline color="primary" size="xs" label="最新のフィールドを取得する"
@click="() => updateFields(sourceAppId!)" />
</div>
</div>
<div class="col-1 q-pl-sm">
キー
</div>
</div>
<q-virtual-scroll style="max-height: 60vh;" :items="mappingProps" separator v-slot="{ item, index }">
<!-- <div class="q-my-sm" v-for="(item, index) in mappingProps" :key="item.id"> -->
<div class="row q-pa-sm q-col-gutter-x-md flex-center">
<div class="col-5">
<ConditionObject :config="config" v-model="item.from" :disabled="item.disabled"
:label="item.disabled ? '「Lookup」によってロックされる' : undefined" />
</div>
<!-- <div class="col-1">
</div> -->
<div class="col-5">
<q-field v-model="item.vName" type="text" outlined dense :disable="item.disabled" >
<!-- <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.fields[0].label}` }}
<span class="text-red" v-if="item.to.fields[0].required">*</span>
<q-tooltip class="bg-yellow-2 text-black shadow-4" >
<div>アプリ : {{ item.to.app.name }}</div>
<div>フィールドのコード : {{ item.to.fields[0].code }}</div>
<div>フィールドのタイプ : {{ item.to.fields[0].type }}</div>
<div v-if="item.to.fields[0].required">必須項目</div>
<!-- <div>フィールド : {{ item.to.fields[0] }}</div>
<div>フィールド : {{ item.isKey }}</div> -->
</q-tooltip>
</div>
</template>
</q-field>
</div>
<div class="col-1">
<q-checkbox size="sm" v-model="item.isKey" :disable="item.disabled" />
<!-- <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> -->
</q-virtual-scroll>
<div class="q-mt-lg q-ml-md row ">
<q-checkbox size="sm" v-model="createWithNull" label="キーが存在しない場合は新規に作成され、存在する場合はデータが更新されます。" />
</div>
</div>
</show-dialog>
</div>
</template>
<script lang="ts">
import { v4 as uuidv4 } from 'uuid';
import { computed, defineComponent, watch, isRef, 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 { IApp, IField } from './AppFieldSelect.vue';
import { api } from 'boot/axios';
type ContextProps = {
props?: {
name: string;
modelValue?: {
app: {
id: string;
name: string;
}
}
}
};
type CurrentModelValueType = {
data: MappingValueType[];
createWithNull: boolean;
}
type MappingValueType = {
id: string;
from: { sharedText?: string };
to: {
app?: IApp,
fields: IField[],
isDialogVisible: boolean;
};
isKey: boolean;
disabled: boolean;
}
const blackListLabelName = ['レコード番号', '作業者', '更新者', '更新日時', '作成日時', '作成者']
export default defineComponent({
name: 'DataMapping',
inheritAttrs: false,
components: {
ShowDialog,
ConditionObject,
AppFieldSelectBox,
FieldSelect
},
props: {
context: {
type: Array<ContextProps>,
default: '',
},
displayName: {
type: String,
default: '',
},
name: {
type: String,
default: '',
},
modelValue: {
type: Object as () => CurrentModelValueType,
},
placeholder: {
type: String,
default: '',
},
onlySourceSelect: {
type: Boolean,
default: false
},
fieldTypes:{
type:Array,
default:()=>[]
},
},
setup(props, { emit }) {
const source = props.context.find(element => element?.props?.name === 'sources')
const sourceApp = computed(() => source?.props?.modelValue?.app);
const sourceAppId = computed(() => sourceApp.value?.id);
const closeDg = () => {
emit('update:modelValue', { data: mappingProps.value, createWithNull: createWithNull.value });
}
const closeToDg = () => {
emit('update:modelValue', { data: mappingProps.value, createWithNull: createWithNull.value });
}
const mappingProps = ref(props.modelValue?.data ?? []);
const createWithNull = ref(props.modelValue?.createWithNull ?? false)
// 外部ソースコンポーネントの appid をリッスンし、変更されたときに現在のコンポーネントを更新します
watch(() => sourceAppId.value, async (newId,) => {
if (!newId) return;
updateFields(newId)
})
const updateFields = async (sourceAppId: string) => {
const ktAppFields = await api.get('api/v1/appfields', {
params: {
app: sourceAppId
}
}).then(res => {
return Object.values(res.data.properties)
// kintoneのデフォルトの非表示フィールドフィルタリング
.filter(f => !blackListLabelName.find(label => f.label === label))
.map(f => ({ name: f.label, objectType: 'field', ...f }))
.map(f => {
// 更新前の値を求める
const beforeData = mappingProps.value.find(m => m.to.fields[0].code === f.code)
return {
id: uuidv4(),
from: beforeData?.from ?? {}, // 以前のデータを入力します
to: {
app: sourceApp.value,
fields: [f],
isDialogVisible: false
},
isKey: beforeData?.isKey ?? false, // 以前のデータを入力します
disabled: false
}
})
})
// 「ルックアップ」によってロックされているフィールドを検索する
const lookupFixedField = ktAppFields
.filter(field => field.to.fields[0].lookup !== undefined)
.flatMap(field => field.to.fields[0].lookup.fieldMappings.map((m) => m.field))
// 「ルックアップ」でロックされたビューコンポーネントを非対話型に設定します
if (lookupFixedField.length > 0) {
ktAppFields.filter(f => lookupFixedField.includes(f.to.fields[0].code)).forEach(f => f.disabled = true)
}
mappingProps.value = ktAppFields
}
const mappingObjectsInputDisplay = computed(() =>
(mappingProps.value && Array.isArray(mappingProps.value)) ?
mappingProps.value
.filter(item => item.from?.sharedText && item.to.fields?.length > 0)
.map(item => {
return `field(${item.to.app?.id},${item.to.fields[0].label}) = ${item.from.sharedText} `;
})
: []
);
const btnDisable = computed(() => props.onlySourceSelect ? !(source?.props?.modelValue?.app?.id) : false);
watchEffect(() => {
emit('update:modelValue', { data: mappingProps.value, createWithNull: createWithNull.value });
});
return {
uuidv4,
dgIsShow: ref(false),
closeDg,
toDgIsShow: ref(false),
closeToDg,
mappingProps,
createWithNull,
updateFields,
// addMappingObject: () => mappingProps.push(defaultMappingProp()),
// deleteMappingObject,
mappingObjectsInputDisplay,
sourceApp,
sourceAppId,
btnDisable,
config: {
canInput: false,
buttonsConfig: [
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
{ label: '変数', color: 'green', type: 'VariableAdd', editable: false },
]
}
};
},
});
</script>
<style lang="scss"></style>

View File

@@ -0,0 +1,239 @@
<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-pa-sm">
<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?: {
sharedText: string;
objectType: 'field';
};
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;
}));
provide('sourceApp', computed(() => source.props?.modelValue?.app?.id));
}
const actionName = props.context.find(element => element?.props?.name === 'displayName')
const processingProps: ValueType = props.modelValue && props.modelValue.vars
? reactive(props.modelValue)
: reactive({
name: '',
actionName: actionName?.props?.modelValue as string,
displayName: '結果(戻り値)',
vars: [
{
id: uuidv4(),
field:{
objectType:'field',
sharedText:''
}
}]
});
const closeDg = () => {
emit('update:modelValue', processingProps);
}
const processingObjects = processingProps.vars;
const deleteProcessingObject = (index: number) => {
if(processingObjects.length >0){
processingObjects.splice(index, 1);
}
if(processingObjects.length===0){
addProcessingObject();
}
}
const processingObjectsInputDisplay = computed(() =>
processingObjects ?
processingObjects
.filter(item => item.field && item.logicalOperator && item.vName)
.map(item => {
return`var(${processingProps.name}.${item.vName}) = ${item.field?.sharedText}`
})
: []
);
const addProcessingObject=()=>{
processingObjects.push({
id: uuidv4(),
field:{
objectType:'field',
sharedText:''
}
});
}
//集計処理方法
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,
deleteProcessingObject,
logicalOperators,
processingObjectsInputDisplay,
};
},
});
</script>
<style lang="scss"></style>

View File

@@ -1,18 +1,19 @@
<template>
<q-input v-model="selectedDate" :label="placeholder" mask="date" :rules="['date']">
<template v-slot:append>
<q-icon name="event" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-date v-model="selectedDate">
<div class="row items-center justify-end">
<q-btn v-close-popup label="Close" color="primary" flat />
</div>
</q-date>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
<div v-bind="$attrs">
<q-input v-model="selectedDate" :label="displayName" :placeholder="placeholder" label-color="primary" mask="date" :rules="['date']" stack-label>
<template v-slot:append>
<q-icon name="event" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-date v-model="selectedDate">
<div class="row items-center justify-end">
<q-btn v-close-popup label="Close" color="primary" flat />
</div>
</q-date>
</q-popup-proxy>
</q-icon>
</template>
</q-input>
</div>
</template>
<script lang="ts">
@@ -20,11 +21,24 @@ import { defineComponent, ref ,watchEffect} from 'vue';
export default defineComponent({
name: 'DatePicker',
inheritAttrs:false,
props: {
displayName:{
type: String,
default: '',
},
name:{
type: String,
default: '',
},
placeholder: {
type: String,
default: '',
},
hint:{
type: String,
default: '',
},
modelValue: {
type: String,
default: '',

View File

@@ -0,0 +1,83 @@
<template>
<div v-bind="$attrs">
<q-input :label="displayName" v-model="inputValue" label-color="primary" :placeholder="placeholder" stack-label>
<template v-slot:append>
<q-btn round dense flat icon="add" @click="addButtonEvent()" />
</template>
</q-input>
</div>
</template>
<script lang="ts">
import { defineComponent,ref,watchEffect } from 'vue';
import { useFlowEditorStore } from '../../stores/flowEditor';
import { IKintoneEventGroup,kintoneEvent } from 'src/types/KintoneEvents';
export default defineComponent({
name: 'EventSetter',
inheritAttrs:false,
props: {
displayName:{
type: String,
default: '',
},
name:{
type: String,
default: '',
},
placeholder: {
type: String,
default: '',
},
hint:{
type: String,
default: '',
},
modelValue: {
type: String,
default: '',
},
connectProps:{
type:Object,
default:undefined
}
},
setup(props , { emit }) {
const inputValue = ref(props.modelValue);
const store = useFlowEditorStore();
const addButtonEvent=()=>{
const eventId =store.currentFlow?.getRoot()?.name;
if(eventId===undefined){return;}
let displayName = inputValue.value;
if(props.connectProps!==undefined && "displayName" in props.connectProps){
displayName =props.connectProps["displayName"].props.modelValue;
}
const customButtonId=`${eventId}.customButtonClick`;
const findedEvent = store.eventTree.findEventById(customButtonId);
if(findedEvent && "events" in findedEvent){
const customEvents = findedEvent as IKintoneEventGroup;
const addEventId = customButtonId+"." + inputValue.value;
if(store.eventTree.findEventById(addEventId)){
return;
}
customEvents.events.push({
eventId: addEventId,
label: displayName,
parentId: customButtonId,
header: 'DELETABLE'
});
}
}
watchEffect(() => {
emit('update:modelValue', inputValue.value);
});
return {
inputValue,
addButtonEvent
};
},
});
</script>

View File

@@ -1,34 +1,72 @@
<template>
<q-input v-model="selectedField" :label="placeholder">
<template v-slot:append>
<q-icon name="search" class="cursor-pointer" @click="showDg"/>
</template>
</q-input>
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeDg">
<field-select ref="appDg" name="フィールド" type="single" :appId="store.appInfo?.appId"></field-select>
</show-dialog>
<div v-bind="$attrs">
<q-field v-model="selectedField" :label="displayName" labelColor="primary" :clearable="isSelected" stack-label
:bottom-slots="!isSelected">
<template v-slot:control>
<q-chip color="primary" text-color="white" v-if="isSelected">
{{ selectedField.name }}
</q-chip>
</template>
<template v-slot:hint v-if="!isSelected">
{{ placeholder }}
</template>
<template v-slot:append>
<q-icon name="search" class="cursor-pointer" color="primary" @click="showDg" />
</template>
</q-field>
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeDg" widht="400px">
<field-select ref="appDg" name="フィールド" :type="selectType" :appId="store.appInfo?.appId" :fieldTypes="fieldTypes"></field-select>
</show-dialog>
</div>
</template>
<script lang="ts">
import { defineComponent, ref ,watchEffect} from 'vue';
import { defineComponent, ref, watchEffect, computed } from 'vue';
import ShowDialog from '../ShowDialog.vue';
import FieldSelect from '../FieldSelect.vue';
import { useFlowEditorStore } from 'stores/flowEditor';
interface IField {
name: string,
code: string,
type: string
}
export default defineComponent({
name: 'FieldInput',
inheritAttrs:false,
components: {
ShowDialog,
FieldSelect,
},
props: {
displayName: {
type: String,
default: '',
},
name: {
type: String,
default: '',
},
placeholder: {
type: String,
default: '',
},
modelValue: {
selectType:{
type:String,
default:'single'
},
fieldTypes:{
type:Array,
default:()=>[]
},
hint: {
type: String,
default: '',
},
modelValue: {
type: Object,
default: null
},
},
setup(props, { emit }) {
@@ -36,14 +74,17 @@ export default defineComponent({
const show = ref(false);
const selectedField = ref(props.modelValue);
const store = useFlowEditorStore();
const isSelected = computed(() => {
return selectedField.value !== null && typeof selectedField.value === 'object' && ('name' in selectedField.value)
});
const showDg = () => {
show.value = true;
};
const closeDg = (val:string) => {
const closeDg = (val: string) => {
if (val == 'OK') {
selectedField.value = appDg.value.selected[0].name;
selectedField.value = appDg.value.selected[0];
}
};
@@ -58,6 +99,7 @@ export default defineComponent({
showDg,
closeDg,
selectedField,
isSelected
};
}
});

View File

@@ -1,33 +1,103 @@
<template>
<q-input :label="placeholder" v-model="inputValue"/>
<div v-bind="$attrs">
<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">
<div class="hint-text" v-html="hint" />
</q-tooltip>
</q-icon>
</template>
</q-input>
</div>
</template>
<script lang="ts">
import { defineComponent,ref,watchEffect } from 'vue';
import { kMaxLength } from 'buffer';
import { defineComponent, ref, watchEffect, computed } from 'vue';
export default defineComponent({
name: 'InputText',
inheritAttrs: false,
props: {
displayName: {
type: String,
default: '',
},
name: {
type: String,
default: '',
},
placeholder: {
type: String,
default: '',
},
modelValue: {
fieldTypes:{
type:Array,
default:()=>[]
},
hint: {
type: String,
default: '',
},
maxLength: {
type: Number,
default: undefined
},
//例:[val=>!!val ||'入力してください']
rules: {
type: String,
default: undefined
},
modelValue: {
// type: Any,
default: '',
},
},
setup(props, { emit }) {
const inputValue = ref(props.modelValue);
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,
showhint: ref(false),
rulesExp
};
},
});
</script>
<style lang="scss">
.hint-text {
white-space: always;
max-width: 450px;
font-size: 1.2em;
}
</style>

View File

@@ -0,0 +1,49 @@
<template>
<div v-bind="$attrs">
<q-input :label="displayName" label-color="primary" v-model="inputValue" :placeholder="placeholder" autogrow
stack-label />
</div>
</template>
<script lang="ts">
import { defineComponent, ref, watchEffect } from 'vue';
export default defineComponent({
name: 'MuiltInputText',
inheritAttrs: false,
props: {
displayName: {
type: String,
default: '',
},
name: {
type: String,
default: '',
},
placeholder: {
type: String,
default: '',
},
hint: {
type: String,
default: '',
},
modelValue: {
type: String,
default: '',
},
},
setup(props, { emit }) {
const inputValue = ref(props.modelValue);
watchEffect(() => {
emit('update:modelValue', inputValue.value);
});
return {
inputValue,
};
},
});
</script>

View File

@@ -0,0 +1,87 @@
<template>
<div class="" v-bind="$attrs">
<q-input v-model.number="numValue" type="number" :label="displayName" label-color="primary" stack-label bottom-slots
:min="min"
:max="max"
:rules="rulesExp"
>
<template v-slot:hint>
{{ placeholder }}
</template>
</q-input>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, ref, watchEffect } from 'vue';
export default defineComponent({
name: 'NumInput',
inheritAttrs:false,
components: {
},
props: {
displayName: {
type: String,
default: '',
},
name: {
type: String,
default: '',
},
placeholder: {
type: String,
default: '',
},
hint: {
type: String,
default: '',
},
min:{
type:Number,
default:undefined
},
max:{
type:Number,
default:undefined
},
//[val=>!!val ||'数値を入力してください',val=>val<=100 && val>=1 || '1-100の範囲内の数値を入力してください']
rules:{
type:String,
default:undefined
},
modelValue: {
type: [Number , String],
default: undefined
},
},
setup(props, { emit }) {
const numValue = ref(props.modelValue);
const rulesExp = props.rules===undefined?null : eval(props.rules);
const isError = computed(()=>{
const val = numValue.value;
if (val === undefined) {
return false;
}
const numVal = typeof val === "string" ? parseInt(val) : val;
// Ensure parsed value is a valid number
if (isNaN(numVal)) {
return true;
}
// Check against min and max boundaries, if defined
if ((props.min !== undefined && numVal < props.min) || (props.max !== undefined && numVal > props.max)) {
return true;
}
return false;
});
watchEffect(()=>{
emit("update:modelValue",numValue.value);
});
return {
numValue,
rulesExp
};
}
});
</script>

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" v-model="item.props.modelValue"></component>
<div v-for="(item, index) in properties" :key="index" >
<component :is="item.component" v-bind="item.props" :context="properties" :connectProps="connectProps(item.props)" v-model="item.props.modelValue"></component>
</div>
</div>
</template>
@@ -15,7 +15,16 @@ import InputText from '../right/InputText.vue';
import SelectBox from '../right/SelectBox.vue';
import DatePicker from '../right/DatePicker.vue';
import FieldInput from '../right/FieldInput.vue';
import { IActionNode,IActionProperty } from 'src/types/ActionTypes';
import AppFieldSelect from './AppFieldSelect.vue';
import MuiltInputText from '../right/MuiltInputText.vue';
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({
name: 'PropertyList',
@@ -23,7 +32,16 @@ export default defineComponent({
InputText,
SelectBox,
DatePicker,
FieldInput
FieldInput,
AppFieldSelect,
MuiltInputText,
ConditionInput,
EventSetter,
ColorPicker,
NumInput,
DataProcessing,
DataMapping,
AppSelect
},
props: {
nodeProps: {
@@ -36,10 +54,25 @@ export default defineComponent({
}
},
setup(props, context) {
const properties=ref(props.nodeProps)
const properties=ref(props.nodeProps);
const connectProps=(props:IProp)=>{
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);
if(targetProp){
connProps[connProp.key]=targetProp;
}
}
}
return connProps;
}
return {
properties
properties,
connectProps
}
}
});
</script>
<style lang="scss">
</style>

View File

@@ -11,24 +11,24 @@
elevated
overlay
>
<q-card class="column full-height" style="width: 300px">
<q-card class="column" style="max-width: 300px;min-height: 100%">
<q-card-section>
<div class="text-h6">プロパティ</div>
<div class="text-h6">{{ actionNode?.subTitle }}設定</div>
</q-card-section>
<q-card-section class="col q-pt-none">
<property-list :node-props="actionProps" v-if="showPanel" ></property-list>
</q-card-section>
<q-card-actions align="right" class="bg-white text-teal">
<q-btn flat label="Save" @click="save"/>
<q-btn flat label="Cancel" @click="cancel" />
<q-btn flat label="キャンセル" @click="cancel" outline dense padding="none sm" color="primary"/>
<q-btn flat label="更新" @click="save" outline dense padding="none sm" color="primary" />
</q-card-actions>
</q-card>
</q-drawer>
</div>
</template>
<script lang="ts">
import { reactive, ref,defineComponent, defineProps,PropType ,watchEffect} from 'vue'
import { ref,defineComponent, PropType ,watchEffect} from 'vue'
import PropertyList from 'components/right/PropertyList.vue';
import { IActionNode } from 'src/types/ActionTypes';
export default defineComponent({
@@ -47,24 +47,24 @@ import { IActionNode } from 'src/types/ActionTypes';
}
},
emits: [
"update:drawerRight"
'update:drawerRight'
],
setup(props,{emit}) {
const showPanel =ref(props.drawerRight);
const actionProps =ref(props.actionNode.actionProps);
const actionProps =ref(props.actionNode?.actionProps);
watchEffect(() => {
showPanel.value = props.drawerRight;
actionProps.value= props.actionNode.actionProps;
actionProps.value= props.actionNode?.actionProps;
});
const cancel = async() =>{
showPanel.value = false;
emit("update:drawerRight",false )
emit('update:drawerRight',false )
}
const save = async () =>{
showPanel.value=false;
emit("update:drawerRight",false )
emit('update:drawerRight',false )
}
return {

View File

@@ -1,13 +1,21 @@
<template>
<q-select v-model="selectedValue" :label="placeholder" :options="options"/>
<div v-bind="$attrs">
<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',
inheritAttrs:false,
props: {
displayName:{
type: String,
default: '',
},
placeholder: {
type: String,
default: '',
@@ -16,20 +24,27 @@ export default defineComponent({
type: Array,
required: true,
},
selectType:{
type:String,
default:'',
},
modelValue: {
type: String,
default: '',
type: [Array,String],
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

@@ -0,0 +1,22 @@
import { api } from 'boot/axios';
export class Auth
{
async login(user:string,pwd:string):Promise<boolean>
{
const params = new URLSearchParams();
params.append('username', user);
params.append('password', pwd);
try{
const result = await api.post(`api/token`,params);
console.info(result);
localStorage.setItem('Token', result.data.access_token);
return true;
}catch(e)
{
console.info(e);
return false;
}
}
}

View File

@@ -1,37 +1,59 @@
import { api } from 'boot/axios';
import { ActionFlow } from 'src/types/ActionTypes';
export class FlowCtrl
{
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 [];
}
async getFlows(appId:string):Promise<ActionFlow[]>
{
const result = await api.get(`http://127.0.0.1:8000/api/flows/${appId}`);
//console.info(result.data);
if(!result.data || !Array.isArray(result.data)){
return [];
}
const flows:ActionFlow[]=[];
for(const flow of result.data){
flows.push(ActionFlow.fromJSON(flow.content));
}
return flows;
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('http://127.0.0.1:8000/api/flow',jsonData);
console.info(result.data)
return true;
}
async UpdateFlow(jsonData:any):Promise<boolean>
{
const result = await api.put('http://127.0.0.1:8000/api/flow/' + jsonData.flowid,jsonData);
console.info(result.data)
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);
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;
}
/**
* デプロイ
* @param appid
* @returns
*/
async depoly(appid: string): Promise<boolean> {
const result = await api.post(`api/v1/createjstokintone?app=${appid}`);
console.info(result.data);
return true;
}
}

View File

@@ -1 +1,25 @@
// app global css in SCSS form
::-webkit-scrollbar {
height: 12px;
width: 14px;
background: transparent;
z-index: 12;
overflow: visible;
}
::-webkit-scrollbar-thumb {
width: 10px;
background-color: #c1c1c1;
border-radius: 10px;
z-index: 12;
border: 4px solid rgba(0, 0, 0, 0);
background-clip: padding-box;
transition: background-color .32s ease-in-out;
margin: 4px;
min-height: 32px;
min-width: 32px;
}
::-webkit-scrollbar-thumb:hover {
background: #c1c1c1;
}

View File

@@ -11,14 +11,16 @@
@click="toggleLeftDrawer"
/>
<q-toolbar-title>
Kintone App Builder
<q-badge align="top" outline>V{{ env.version }}</q-badge>
{{ productName }}
<q-badge align="top" outline>V{{ version }}</q-badge>
</q-toolbar-title>
<domain-selector></domain-selector>
<q-btn flat round dense icon="logout" @click="authStore.logout()"/>
</q-toolbar>
</q-header>
<q-drawer
v-model="leftDrawerOpen"
:model-value="authStore.toggleLeftDrawer"
:show-if-above="false"
bordered
>
@@ -26,7 +28,7 @@
<q-item-label
header
>
Essential Links
関連リンク
</q-item-label>
<EssentialLink
@@ -46,6 +48,10 @@
<script setup lang="ts">
import { ref } from 'vue';
import EssentialLink, { EssentialLinkProps } from 'components/EssentialLink.vue';
import DomainSelector from 'components/DomainSelector.vue';
import { useAuthStore } from 'stores/useAuthStore';
const authStore = useAuthStore();
const essentialLinks: EssentialLinkProps[] = [
{
@@ -59,14 +65,14 @@ const essentialLinks: EssentialLinkProps[] = [
title: 'フローエディター',
caption: 'flowChart',
icon: 'account_tree',
link: '/#/flowEditor2',
link: '/#/FlowChart',
target:'_self'
},
{
title: 'FlowEditor',
caption: 'FlowEditor',
icon: 'account_tree',
link: '/#/flowEditor',
title: '条件エディター',
caption: 'condition',
icon: 'tune',
link: '/#/condition',
target:'_self'
},
{
@@ -145,11 +151,10 @@ const essentialLinks: EssentialLinkProps[] = [
}
];
const leftDrawerOpen = ref(false)
const env=process.env;
const version = process.env.version;
const productName = process.env.productName;
function toggleLeftDrawer() {
leftDrawerOpen.value = !leftDrawerOpen.value
authStore.toggleLeftMenu();
}
</script>

View File

@@ -1,160 +1,274 @@
<template>
<div class="q-pa-md q-gutter-sm event-tree">
<q-drawer
side="left"
overlay
bordered
v-model="drawerLeft"
:show-if-above="false"
elevated
>
<!-- <q-card class="column full-height" style="width: 300px">
<q-card-section> -->
<div class="flex-center fixd-top" >
<AppSelector />
</div>
<!-- </q-card-section> -->
<q-separator />
<!-- <q-card-section> -->
<div class="flex-center">
<EventTree />
</div>
<!-- </q-card-section> -->
<!-- </q-card> -->
</q-drawer>
</div>
<q-page>
<div class="q-pa-md q-gutter-sm">
<div class="flowchart" v-if="store.currentFlow">
<node-item v-for="(node,) in store.currentFlow.actionNodes" :key="node.id"
:isSelected="node===state.activeNode" :actionNode="node"
@addNode="addNode"
@nodeSelected="onNodeSelected"
@nodeEdit="onNodeEdit"
@deleteNode="onDeleteNode"
@deleteAllNextNodes="onDeleteAllNextNodes"
></node-item>
</div>
</div>
</q-page>
<PropertyPanel :actionNode="state.activeNode" v-model:drawerRight="drawerRight"></PropertyPanel>
<ShowDialog v-model:visible="showAddAction" name="アクション" @close="closeDg">
<action-select ref="appDg" name="model" type="single"></action-select>
</ShowDialog>
<q-layout container class="absolute-full shadow-2 rounded-borders">
<div class="q-pa-sm q-gutter-sm ">
<q-drawer side="left" :overlay="true" bordered v-model="drawerLeft" :show-if-above="false" elevated>
<div class="flex-center fixed-top app-selector">
<AppSelector />
</div>
<div class="flex-center absolute-full" style="padding-top:65px;padding-left:15px;padding-right:15px;">
<q-scroll-area class="fit" :horizontal-thumb-style="{ opacity: '0' }">
<EventTree />
</q-scroll-area>
</div>
<div class="flex-center fixed-bottom bg-grey-3 q-pa-md row ">
<q-btn color="secondary" glossy label="デプロイ" @click="onDeploy" icon="sync" :loading="deployLoading" />
<q-space></q-space>
<q-btn color="primary" label="保存" @click="onSaveFlow" icon="save" :loading="saveLoading" />
</div>
</q-drawer>
</div>
<q-btn flat dense round
:icon="drawerLeft?'keyboard_double_arrow_left':'keyboard_double_arrow_right'"
:style="[drawerLeft?{'left':'300px'}:{'left':'0px'}]"
@click="drawerLeft=!drawerLeft" class="expand" />
<div class="q-pa-md q-gutter-sm" :style="{minWidth: minPanelWidth}">
<div class="flowchart" v-if="store.currentFlow" :style="[drawerLeft?{paddingLeft:'300px'}:{}]">
<node-item v-if="rootNode!==undefined" :key="rootNode.id" :isSelected="rootNode === store.activeNode"
:actionNode="rootNode" @addNode="addNode" @nodeSelected="onNodeSelected" @nodeEdit="onNodeEdit"
@deleteNode="onDeleteNode" @deleteAllNextNodes="onDeleteAllNextNodes" @copyFlow="onCopyFlow"></node-item>
</div>
</div>
<PropertyPanel :actionNode="store.activeNode" v-model:drawerRight="drawerRight"></PropertyPanel>
</q-layout>
<ShowDialog v-model:visible="showAddAction" name="アクション" @close="closeDg" min-width="500px" min-height="500px">
<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>
<action-select ref="appDg" name="model" :filter="filter" type="single"></action-select>
</ShowDialog>
</q-page>
</template>
<script setup lang="ts">
import {ref,reactive,computed,onMounted} from 'vue';
import {IActionNode, ActionNode, IActionFlow, ActionFlow,RootAction, IActionProperty } from 'src/types/ActionTypes';
import { ref, reactive, computed, onMounted } from 'vue';
import { IActionNode, ActionNode, IActionFlow, ActionFlow, RootAction, IActionProperty } from 'src/types/ActionTypes';
import { storeToRefs } from 'pinia';
import { useFlowEditorStore } from 'stores/flowEditor';
import { useAuthStore } from 'stores/useAuthStore';
import NodeItem from 'src/components/main/NodeItem.vue';
import ShowDialog from 'components/ShowDialog.vue';
import ActionSelect from 'components/ActionSelect.vue';
import PropertyPanel from 'components/right/PropertyPanel.vue';
import AppSelector from 'components/left/AppSelector.vue';
import EventTree from 'components/left/EventTree.vue';
import {FlowCtrl } from '../control/flowctrl';
const drawerLeft = ref(true);
import { FlowCtrl } from '../control/flowctrl';
import { useQuasar } from 'quasar';
const deployLoading = ref(false);
const saveLoading = ref(false);
const drawerLeft = ref(false);
const $q = useQuasar();
const store = useFlowEditorStore();
// ref関数を使ってtemplateとバインド
const state=reactive({
activeNode:{
id:""
},
})
const authStore = useAuthStore();
const appDg = ref();
const prevNodeIfo=ref({
prevNode:{} as IActionNode,
inputPoint:""
const prevNodeIfo = ref({
prevNode: {} as IActionNode,
inputPoint: ""
});
const refFlow = ref<ActionFlow|null>(null);
const showAddAction=ref(false);
const drawerRight=ref(false);
const model=ref("");
const addActionNode=(action:IActionNode)=>{
// const refFlow = ref<ActionFlow|null>(null);
const showAddAction = ref(false);
const drawerRight = ref(false);
const filter=ref("");
const model = ref("");
const addActionNode = (action: IActionNode) => {
// refFlow.value?.actionNodes.push(action);
store.currentFlow?.actionNodes.push(action);
store.currentFlow?.actionNodes.push(action);
}
const addNode=(node:IActionNode,inputPoint:string)=>{
showAddAction.value=true;
prevNodeIfo.value.prevNode=node;
prevNodeIfo.value.inputPoint=inputPoint;
}
const onNodeSelected=(node:IActionNode)=>{
//右パネルが開いている場合、自動閉じる
if(drawerRight.value && state.activeNode.id!==node.id){
drawerRight.value=false;
const rootNode = computed(()=>{
return store.currentFlow?.getRoot();
});
const minPanelWidth=computed(()=>{
const root = store.currentFlow?.getRoot();
if(store.currentFlow && root){
return store.currentFlow?.getColumns(root) * 300 + 'px';
}else{
return "300px";
}
state.activeNode = node;
});
const addNode = (node: IActionNode, inputPoint: string) => {
if (drawerRight.value) {
drawerRight.value = false;
}
showAddAction.value = true;
prevNodeIfo.value.prevNode = node;
prevNodeIfo.value.inputPoint = inputPoint;
}
const onNodeEdit=(node:IActionNode)=>{
state.activeNode = node;
drawerRight.value=true;
const onNodeSelected = (node: IActionNode) => {
//右パネルが開いている場合、自動閉じる
if (drawerRight.value && store.activeNode?.id !== node.id) {
drawerRight.value = false;
}
store.setActiveNode(node);
}
const onDeleteNode=(node:IActionNode)=>{
if(!store.currentFlow) return;
const onNodeEdit = (node: IActionNode) => {
store.setActiveNode(node);
drawerRight.value = true;
}
const onDeleteNode = (node: IActionNode) => {
if (!store.currentFlow) return;
//右パネルが開いている場合、自動閉じる
if (drawerRight.value && store.activeNode?.id === node.id) {
drawerRight.value = false;
}
store.currentFlow?.removeNode(node);
}
const onDeleteAllNextNodes=(node:IActionNode)=>{
if(!store.currentFlow) return;
const onDeleteAllNextNodes = (node: IActionNode) => {
if (!store.currentFlow) return;
//右パネルが開いている場合、自動閉じる
if (drawerRight.value) {
drawerRight.value = false;
}
store.currentFlow?.removeAllNext(node.id);
}
const closeDg=(val :any)=>{
console.log("Dialog closed->",val);
const closeDg = (val: any) => {
console.log("Dialog closed->", val);
if (val == 'OK') {
const data = appDg.value.selected[0];
const actionProps=JSON.parse(data.content);
const action = new ActionNode(data.name,data.desc,"",[],actionProps);
store.currentFlow?.addNode(action, prevNodeIfo.value.prevNode,prevNodeIfo.value.inputPoint);
const data = appDg.value.selected[0];
const actionProps = JSON.parse(data.property);
const outputPoint = JSON.parse(data.outputPoints);
const action = new ActionNode(data.name, data.desc, "", outputPoint, actionProps);
store.currentFlow?.addNode(action, prevNodeIfo.value.prevNode, prevNodeIfo.value.inputPoint);
}
}
/*
*フローのデータをコピーする
*/
const onCopyFlow = () => {
if (navigator.clipboard) {
const jsonData =JSON.stringify(store.currentFlow) ;
navigator.clipboard.writeText(jsonData).then(() => {
console.log('Text successfully copied to clipboard');
},
(err) => {
console.error('Error in copying text: ', err);
});
} else {
console.log('Clipboard API not available');
}
};
/**
* デプロイ
*/
const onDeploy = async () => {
if (store.appInfo === undefined || store.flows?.length === 0) {
$q.notify({
type: 'negative',
caption: "エラー",
message: `設定されたフローがありません。`
});
return;
}
try {
deployLoading.value = true;
await store.deploy();
deployLoading.value = false;
$q.notify({
type: 'positive',
caption: "通知",
message: `デプロイを成功しました。`
});
} catch (error) {
console.error(error);
deployLoading.value = false;
$q.notify({
type: 'negative',
caption: "エラー",
message: `デプロイが失敗しました。`
})
}
return;
}
const fetchData= async ()=>{
const onSaveFlow = async () => {
const targetFlow = store.selectedFlow;
if (targetFlow === undefined) {
$q.notify({
type: 'negative',
caption: "エラー",
message: `編集中のフローがありません。`
});
return;
}
try {
saveLoading.value = true;
await store.saveFlow(targetFlow);
saveLoading.value = false;
$q.notify({
type: 'positive',
caption: "通知",
message: `${targetFlow.getRoot()?.subTitle}のフロー設定を保存しました。`
});
} catch (error) {
console.error(error);
saveLoading.value = false;
$q.notify({
type: 'negative',
caption: "エラー",
message: `${targetFlow.getRoot()?.subTitle}のフローの設定の保存が失敗しました。`
})
}
}
const fetchData = async () => {
drawerLeft.value = true;
if (store.appInfo === undefined) return;
const flowCtrl = new FlowCtrl();
if(store.appInfo===undefined) return;
const actionFlows = await flowCtrl.getFlows(store.appInfo?.appId);
if(actionFlows && actionFlows.length>0){
if (actionFlows && actionFlows.length > 0) {
store.setFlows(actionFlows);
}
if(actionFlows && actionFlows.length==1){
if (actionFlows && actionFlows.length == 1) {
store.selectFlow(actionFlows[0]);
}
refFlow.value=actionFlows[0];
const root =refFlow.value.getRoot();
if(root){
state.activeNode=root;
const root = actionFlows[0].getRoot();
if (root) {
store.setActiveNode(root);
}
}
onMounted(() => {
authStore.toggleLeftMenu();
fetchData();
});
</script>
<style lang="scss">
.flowchart{
padding-top: 10px;
}
.flow-toolbar{
.app-selector {
padding: 15px;
z-index: 999;
}
.flowchart {
padding-top: 10px;
}
.flow-toolbar {
opacity: 50%;
}
.flow-container{
height: 91.5dvb;
overflow: hidden;
}
.event-tree .q-drawer {
top:50px;
z-index: 999;
}
}
.event-tree .q-drawer {
top: 50px;
z-index: 999;
}
.expand{
position: fixed;
left: 0px;
top: 50%;
z-index: 9999;
}
</style>

View File

@@ -13,7 +13,7 @@
</div>
</q-page>
<PropertyPanel :actionNode="state.activeNode" v-model:drawerRight="drawerRight"></PropertyPanel>
<show-dialog v-model:visible="showAddAction" name="アクション" @close="closeDg">
<show-dialog v-model:visible="showAddAction" name="アクション" @close="closeDg" width="350px">
<action-select ref="appDg" name="アクション" type="single"></action-select>
</show-dialog>
</template>

View File

@@ -1,117 +0,0 @@
<template>
<div >
<div class="q-ma-md">
<div class="q-gutter-xs row items-start">
<q-btn
size="md"
@click="drawerLeft = !drawerLeft"
icon="keyboard_double_arrow_right"
round
/>
<q-space />
<q-btn
color="white"
size="sm"
text-color="black"
label="キャンセル"
dense
/>
<q-btn
class="q-px-sm"
color="primary"
size="sm"
label="保存する"
@click="save()"
dense
/>
</div>
</div>
<q-layout
container
class="flow-container shadow-2 rounded-borders"
>
<q-drawer side="left" overlay bordered v-model="drawerLeft">
<div class="q-pa-sm fixed-right">
<q-btn
flat
round
color="primary"
icon="close"
@click="drawerLeft = !drawerLeft"
/>
</div>
<div class="q-mt-lg q-pa-sm">
<q-card-section>
<div class="flex-center">
<ItemSelector />
</div>
</q-card-section>
</div>
<q-separator />
<div class="q-mt-md q-pa-sm">
<q-card-section>
<ControlPanel />
</q-card-section>
</div>
<q-separator />
<q-card-actions align="right">
<div class="q-pa-sm">
<q-btn
flat
color="primary"
size="md"
@click="drawerLeft = !drawerLeft"
label="ジャンプ"
dense
/>
</div>
</q-card-actions>
</q-drawer>
<FlowChartTest />
</q-layout>
</div>
</template>
<script setup lang="ts">
import FlowChartTest from 'pages/FlowChartTest.vue';
import ControlPanel from 'components/flowEditor/left/ControlPanelC.vue';
import ItemSelector from 'components/flowEditor/left/ItemSelector.vue';
import { ref } from 'vue';
import { storeToRefs } from 'pinia';
import { useFlowEditorStore } from 'stores/flowEditor';
import { FlowCtrl } from '../control/flowctrl'
const flowCtrl = new FlowCtrl();
const actName = ref('勤怠管理 - 4');
const drawerLeft = ref(false);
const store = useFlowEditorStore();
const { flowNames1 } = storeToRefs(store);
let isNew = ref(true);
const save = () =>{
if(isNew.value)
{
flowCtrl.SaveFlow({appid:'1',flowid:'flow123',eventid:'event123',name:'test',content:'[]'});
isNew.value = false;
}
else
{
flowCtrl.UpdateFlow({appid:'1',flowid:'flow123',eventid:'event123',name:'test',content:'[{"a":"b"}]'});
}
}
</script>
<style lang="scss">
.flow-toolbar{
opacity: 50%;
}
.flow-container{
height: calc(91.5dvb - 50px);
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,112 @@
<template>
<q-layout view="lHh Lpr fff">
<q-page-container>
<q-page class="window-height window-width row justify-center items-center">
<div class="column q-pa-lg">
<div class="row">
<q-card :square="false" class="shadow-24" style="width:400px;height:540px;">
<q-card-section class="bg-primary">
<h4 class="text-h5 text-white q-my-md">{{ title}}</h4>
</q-card-section>
<q-card-section>
<q-form class="q-px-sm q-pt-xl" ref="loginForm">
<q-input square clearable v-model="email" type="email" lazy-rules
:rules="[required,isEmail,short]" label="メール">
<template v-slot:prepend>
<q-icon name="email" />
</template>
</q-input>
<q-input square clearable v-model="password" :type="passwordFieldType" lazy-rules
:rules="[required, short]" label="パスワード">
<template v-slot:prepend>
<q-icon name="lock" />
</template>
<template v-slot:append>
<q-icon :name="visibilityIcon" @click="switchVisibility" class="cursor-pointer" />
</template>
</q-input>
</q-form>
</q-card-section>
<q-card-actions class="q-px-lg">
<q-btn :loading="loading" unelevated size="lg" color="secondary" @click="submit" class="full-width text-white"
label="ログイン" >
<template v-slot:loading>
<q-spinner class="on-left" />
ログイン中...
</template>
</q-btn>
</q-card-actions>
</q-card>
</div>
</div>
</q-page>
</q-page-container>
</q-layout>>
</template>
<script setup lang="ts">
import { useQuasar } from 'quasar'
// import { useRouter } from 'vue-router';
import { ref } from 'vue';
// import { Auth } from '../control/auth'
import { useAuthStore } from 'stores/useAuthStore';
const authStore = useAuthStore();
const $q = useQuasar()
const loginForm = ref(null);
const loading = ref(false);
let title = ref('ログイン');
let email = ref('');
let password = ref('');
let visibility = ref(false);
let passwordFieldType = ref('password');
let visibilityIcon = ref('visibility');
const required = (val:string) => {
return (val && val.length > 0 || '必須項目')
}
const isEmail = (val:string) => {
const emailPattern = /^(?=[a-zA-Z0-9@._%+-]{6,254}$)[a-zA-Z0-9._%+-]{1,64}@(?:[a-zA-Z0-9-]{1,63}\.){1,8}[a-zA-Z]{2,63}$/
return (emailPattern.test(val) || '無効なメールアドレス')
}
const short = (val:string) => {
return (val && val.length > 3 || '値が短く過ぎる')
}
const switchVisibility = () => {
visibility.value = !visibility.value
passwordFieldType.value = visibility.value ? 'text' : 'password'
visibilityIcon.value = visibility.value ? 'visibility_off' : 'visibility'
}
const submit = async () =>{
loading.value=true;
try {
const result = await authStore.login(email.value,password.value);
loading.value=false;
if(result){
$q.notify({
icon: 'done',
color: 'positive',
message: 'ログイン成功'
});
}
else{
$q.notify({
icon: 'error',
color: 'negative',
message: 'ログイン失敗'
});
}
}catch (error) {
console.error(error);
loading.value=false;
$q.notify({
icon: 'error',
color: 'negative',
message: 'ログイン失敗'
});
}
}
</script>

View File

@@ -22,9 +22,9 @@
<div class="q-pa-md">
<q-select v-model="model" :options="options" label="Standard"/>
<q-btn :label="model+'選択'" color="primary" @click="showDg()" />
<show-dialog v-model:visible="show" :name="model" @close="closeDg">
<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

@@ -0,0 +1,206 @@
<template>
<div class="q-pa-md">
<q-table title="Treats" :rows="rows" :columns="columns" row-key="id" selection="single" :filter="filter"
:loading="loading" v-model:selected="selected">
<template v-slot:top>
<q-btn color="primary" :disable="loading" label="新規" @click="addRow" />
<q-btn class="q-ml-sm" color="primary" :disable="loading" label="編集" @click="editRow" />
<q-btn class="q-ml-sm" color="primary" :disable="loading" label="削除" @click="removeRow" />
<q-space />
<q-input borderless dense debounce="300" color="primary" v-model="filter">
<template v-slot:append>
<q-icon name="search" />
</template>
</q-input>
</template>
</q-table>
<q-dialog :model-value="show" persistent>
<q-card style="min-width: 400px">
<q-card-section>
<div class="text-h6">Kintone Account</div>
</q-card-section>
<q-card-section class="q-pt-none">
<q-form class="q-gutter-md">
<q-input filled v-model="tenantid" label="Tenant" hint="Tenant ID" lazy-rules
:rules="[val => val && val.length > 0 || 'Please type something']" />
<q-input filled v-model="name" label="Your name *" hint="Kintone envirment name" lazy-rules
:rules="[val => val && val.length > 0 || 'Please type something']" />
<q-input filled type="url" v-model="url" label="Kintone url" hint="Kintone domain address" lazy-rules
:rules="[val => val && val.length > 0, isDomain || 'Please type something']" />
<q-input filled v-model="kintoneuser" label="Login user " hint="Kintone user name" lazy-rules
:rules="[val => val && val.length > 0 || 'Please type something']" />
<q-input v-model="kintonepwd" filled :type="isPwd ? 'password' : 'text'" hint="Password with toggle"
label="User password">
<template v-slot:append>
<q-icon :name="isPwd ? 'visibility_off' : 'visibility'" class="cursor-pointer" @click="isPwd = !isPwd" />
</template>
</q-input>
</q-form>
</q-card-section>
<q-card-actions align="right" class="text-primary">
<q-btn label="Save" type="submit" color="primary" @click="onSubmit" />
<q-btn label="Cancel" type="cancel" color="primary" flat class="q-ml-sm" @click="closeDg()" />
</q-card-actions>
</q-card>
</q-dialog>
<q-dialog v-model="confirm" persistent>
<q-card>
<q-card-section class="row items-center">
<q-avatar icon="confirm" color="primary" text-color="white" />
<span class="q-ml-sm">削除してもよろしいですか</span>
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="Cancel" color="primary" v-close-popup />
<q-btn flat label="OK" color="primary" v-close-popup @click="deleteDomain()" />
</q-card-actions>
</q-card>
</q-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, reactive } from 'vue';
import { api } from 'boot/axios';
const columns = [
{ name: 'id' },
{
name: 'tenantid',
required: true,
label: 'Tenant',
align: 'left',
field: row => row.tenantid,
format: val => `${val}`,
sortable: true
},
{ name: 'name', align: 'center', label: 'Name', field: 'name', sortable: true },
{ name: 'url', align: 'left', label: 'URL', field: 'url', sortable: true },
{ name: 'user', label: 'Account', field: 'user' },
{ name: 'password', label: 'Password', field: 'password' }
];
const loading = ref(false);
const filter = ref('');
const rows = ref([]);
const show = ref(false);
const confirm = ref(false);
const selected = ref([]);
const tenantid = ref('');
const name = ref('');
const url = ref('');
const isPwd = ref(true);
const kintoneuser = ref('');
const kintonepwd = ref('');
let editId = ref(0);
const getDomain = async () => {
loading.value = true;
const result= await api.get(`api/domains/1`);
rows.value= result.data.map((item)=>{
return { id: item.id, tenantid: item.tenantid, name: item.name, url: item.url, user: item.kintoneuser, password: item.kintonepwd }
});
loading.value = false;
}
onMounted(async () => {
await getDomain();
})
// emulate fetching data from server
const addRow = () => {
editId.value
show.value = true;
}
const removeRow = () => {
//loading.value = true
confirm.value = true;
let row = JSON.parse(JSON.stringify(selected.value[0]));
if (selected.value.length === 0) {
return;
}
editId.value = row.id;
}
const deleteDomain = () => {
api.delete(`api/domain/${editId.value}`).then(() => {
getDomain();
})
editId.value = 0;
selected.value = [];
};
const editRow = () => {
if (selected.value.length === 0) {
return;
}
let row = JSON.parse(JSON.stringify(selected.value[0]));
editId.value = row.id;
tenantid.value = row.tenantid;
name.value = row.name;
url.value = row.url;
kintoneuser.value = row.user;
kintonepwd.value = row.password;
isPwd.value = true;
show.value = true;
};
const closeDg = () => {
show.value = false;
onReset();
}
const onSubmit = () => {
if (editId.value !== 0) {
api.put(`api/domain`, {
'id': editId.value,
'tenantid': tenantid.value,
'name': name.value,
'url': url.value,
'kintoneuser': kintoneuser.value,
'kintonepwd': kintonepwd.value
}).then(() => {
getDomain();
closeDg();
onReset();
})
}
else {
api.post(`api/domain`, {
'id': 0,
'tenantid': tenantid.value,
'name': name.value,
'url': url.value,
'kintoneuser': kintoneuser.value,
'kintonepwd': kintonepwd.value
}).then(() => {
getDomain();
closeDg();
onReset();
})
}
selected.value = [];
}
const onReset = () => {
name.value = '';
url.value = '';
kintoneuser.value = '';
kintonepwd.value = '';
isPwd.value = true;
editId.value = 0;
}
</script>

View File

@@ -0,0 +1,270 @@
<!-- <template>
<div class="q-pa-md" style="max-width: 400px">
<q-form
@submit="onSubmit"
@reset="onReset"
class="q-gutter-md"
>
<q-input
filled
v-model="name"
label="Your name *"
hint="Kintone envirment name"
lazy-rules
:rules="[ val => val && val.length > 0 || 'Please type something']"
/>
<q-input
filled type="url"
v-model="url"
label="Kintone url"
hint="Kintone domain address"
lazy-rules
:rules="[ val => val && val.length > 0,isDomain || 'Please type something']"
/>
<q-input
filled
v-model="username"
label="Login user "
hint="Kintone user name"
lazy-rules
:rules="[ val => val && val.length > 0 || 'Please type something']"
/>
<q-input v-model="password" filled :type="isPwd ? 'password' : 'text'" hint="Password with toggle" label="User password">
<template v-slot:append>
<q-icon
:name="isPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="isPwd = !isPwd"
/>
</template>
</q-input>
<q-toggle v-model="accept" label="Active Domain" />
<div>
<q-btn label="Submit" type="submit" color="primary"/>
<q-btn label="Reset" type="reset" color="primary" flat class="q-ml-sm" />
</div>
</q-form>
</div>
</template>
<script>
import { useQuasar } from 'quasar'
import { ref } from 'vue'
export default {
setup () {
const $q = useQuasar()
const name = ref(null)
const age = ref(null)
const accept = ref(false)
const isPwd =ref(true)
return {
name,
age,
accept,
isPwd,
isDomain(val) {
const domainPattern = /^https?\/\/:([a-zA-Z] +\.){1}([a-zA-Z]+)\.([a-zA-Z]+)$/;
return (domainPattern.test(val) || '無効なURL')
},
onSubmit () {
if (accept.value !== true) {
$q.notify({
color: 'red-5',
textColor: 'white',
icon: 'warning',
message: 'You need to accept the license and terms first'
})
}
else {
$q.notify({
color: 'green-4',
textColor: 'white',
icon: 'cloud_done',
message: 'Submitted'
})
}
},
onReset () {
name.value = null
age.value = null
accept.value = false
}
}
}
}
</script> -->
<template>
<div class="q-pa-md">
<q-table grid grid-header title="Domain" selection="single" :rows="rows" :columns="columns" v-model:selected="selected" row-key="name" :filter="filter" hide-header>
<template v-slot:top>
<div class="q-pa-md q-gutter-sm">
<q-btn color="primary" label="追加" @click="newDomain()" dense />
</div>
<q-space />
<q-input borderless dense debounce="300" v-model="filter" placeholder="Search">
<template v-slot:append>
<q-icon name="search" />
</template>
</q-input>
</template>
<template v-slot:item="props">
<div class="q-pa-xs col-xs-12 col-sm-6 col-md-4">
<q-card>
<q-card-section>
<div class="q-table__grid-item-row">
<div class="q-table__grid-item-title">Domain</div>
<div class="q-table__grid-item-value">{{ props.row.name }}</div>
</div>
<div class="q-table__grid-item-row">
<div class="q-table__grid-item-title">URL</div>
<div class="q-table__grid-item-value">{{ props.row.url }}</div>
</div>
<div class="q-table__grid-item-row">
<div class="q-table__grid-item-title">Account</div>
<div class="q-table__grid-item-value">{{ props.row.kintoneuser }}</div>
</div>
<div class="q-table__grid-item-row">
<div class="q-table__grid-item-value">{{isActive(props.row.id) }}</div>
</div>
</q-card-section>
<q-separator />
<q-card-actions align="right">
<q-btn flat @click = "activeDomain(props.row.id)">有効</q-btn>
<q-btn flat @click = "deleteConfirm(props.row)">削除</q-btn>
</q-card-actions>
</q-card>
</div>
</template>
</q-table>
<show-dialog v-model:visible="show" name="ドメイン" @close="closeDg" width="350px">
<domain-select ref="domainDg" name="ドメイン" type="multiple"></domain-select>
</show-dialog>
<q-dialog v-model="confirm" persistent>
<q-card>
<q-card-section class="row items-center">
<q-avatar icon="confirm" color="primary" text-color="white" />
<span class="q-ml-sm">削除してもよろしいですか</span>
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="Cancel" color="primary" v-close-popup />
<q-btn flat label="OK" color="primary" v-close-popup @click = "deleteDomain()"/>
</q-card-actions>
</q-card>
</q-dialog>
</div>
</template>
<script setup lang="ts">
import { useQuasar } from 'quasar'
import { ref, onMounted, reactive } from 'vue'
import ShowDialog from 'components/ShowDialog.vue';
import DomainSelect from 'components/DomainSelect.vue';
import { useAuthStore } from 'stores/useAuthStore';
const authStore = useAuthStore();
import { api } from 'boot/axios';
import { domain } from 'process';
const $q = useQuasar()
const domainDg = ref();
const selected = ref([])
const show = ref(false);
const confirm = ref(false)
let editId = ref(0);
let activedomainid = ref(0);
const columns = [
{ name: 'id'},
{name: 'name',required: true,label: 'Name',align: 'left',field: 'name',sortable: true},
{ name: 'url', align: 'center', label: 'Domain', field: 'url', sortable: true },
{ name: 'kintoneuser', label: 'User', field: 'kintoneuser', sortable: true },
{ name: 'kintonepwd' },
{ name: 'active', field: 'active'}
]
const rows = ref([] as any[]);
const isActive = (id:number) =>{
if(id == activedomainid.value)
return "Active";
else
return "Inactive";
}
const newDomain = () => {
editId.value = 0;
show.value = true;
};
const activeDomain = (id:number) => {
api.put(`api/activedomain/`+ id).then(() =>{
getDomain();
})
};
const deleteConfirm = (row:object) => {
confirm.value = true;
editId.value = row.id;
};
const deleteDomain = () => {
api.delete(`api/domain/`+ editId.value+'/1').then(() =>{
getDomain();
})
editId.value = 0;
};
const closeDg = (val:string) => {
if (val == 'OK') {
let dodmainids =[];
let domains = JSON.parse(JSON.stringify(domainDg.value.selected));
for(var key in domains)
{
dodmainids.push(domains[key].id);
}
api.post(`api/domain`, dodmainids).then(() =>{getDomain();});
}
};
const getDomain = async () => {
const resp = await api.get(`api/activedomain`);
activedomainid.value = resp.data.id;
const domainResult = await api.get(`api/domain`);
const domains = domainResult.data as any[];
rows.value=domains.map((item)=>{
return { id:item.id,name: item.name, url: item.url, kintoneuser: item.kintoneuser, kintonepwd: item.kintonepwd}
});
}
onMounted(async () => {
await getDomain();
})
const isDomain = (val) =>{
// const domainPattern = /^https\/\/:([a-zA-Z] +\.){1}([a-zA-Z]+)\.([a-zA-Z]+)$/;
// return (domainPattern.test(val) || '無効なURL')
return true;
};
</script>

View File

@@ -0,0 +1,39 @@
<template>
<q-page>
<div class="flowchart">
<q-btn @click="showCondition()" class="q-mt-md" color="primary" icon="mdi-plus">条件エディタ表示</q-btn>
</div>
<condition-editor v-model:show="show" v-model:conditionTree="tree"></condition-editor>
<q-code>{{conditionString}}</q-code>
</q-page>
</template>
<script setup lang="ts">
import {ref,reactive,computed} from 'vue';
import ConditionEditor from '../components/ConditionEditor/ConditionEditor.vue';
import { useFlowEditorStore } from 'stores/flowEditor';
import { ConditionTree,GroupNode,ConditionNode,LogicalOperator,Operator } from 'app/src/types/Conditions';
const store = useFlowEditorStore();
const tree = reactive(new ConditionTree());
const newNode = new ConditionNode({},Operator.Equal,'',tree.root);
tree.addNode(tree.root,newNode);
const show =ref(false);
const showCondition=()=>{
show.value=true;
}
const conditionString = computed(()=>{
return tree.buildConditionString(tree.root);
});
store.setApp({
appId:'146',
name:'トリトン管理部日報'
});
</script>
<style lang="scss">
.flowchart {
padding-top: 10px;
}
</style>

View File

@@ -4,7 +4,7 @@
<div class="q-pa-md column content-center items-center">
<div>
<q-btn label="アクション選択" color="primary" @click="showDg()" v-if="addshow" />
<show-dialog v-model:visible="show" name="アクション" @close="closeDg">
<show-dialog v-model:visible="show" name="アクション" @close="closeDg" width="350px">
<action-select ref="appDg" name="アクション" type="single"></action-select>
</show-dialog>
</div>

View File

@@ -7,7 +7,7 @@ import {
} from 'vue-router';
import routes from './routes';
import { useAuthStore } from 'stores/useAuthStore';
/*
* If not building with SSR mode, you can
* directly export the Router instantiation;
@@ -17,20 +17,60 @@ import routes from './routes';
* with the Router instance.
*/
export default route(function (/* { store, ssrContext } */) {
const createHistory = process.env.SERVER
? createMemoryHistory
: (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory);
const createHistory = process.env.SERVER
? createMemoryHistory
: (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory);
const Router = createRouter({
scrollBehavior: () => ({ left: 0, top: 0 }),
routes,
const routerInstance = createRouter({
scrollBehavior: () => ({ left: 0, top: 0 }),
routes,
// Leave this as is and make changes in quasar.conf.js instead!
// quasar.conf.js -> build -> vueRouterMode
// quasar.conf.js -> build -> publicPath
history: createHistory(process.env.VUE_ROUTER_BASE),
});
return Router;
// Leave this as is and make changes in quasar.conf.js instead!
// quasar.conf.js -> build -> vueRouterMode
// quasar.conf.js -> build -> publicPath
history: createHistory(process.env.VUE_ROUTER_BASE),
});
export default route(function (/* { store, ssrContext } */) {
routerInstance.beforeEach(async (to) => {
// clear alert on route change
//const alertStore = useAlertStore();
//alertStore.clear();
// redirect to login page if not logged in and trying to access a restricted page
const publicPages = ['/login'];
const authRequired = !publicPages.includes(to.path);
const authStore = useAuthStore();
if (authRequired && !authStore.token) {
authStore.returnUrl = to.fullPath;
return '/login';
}
});
return routerInstance;
});
export const router = routerInstance;
// const createHistory = process.env.SERVER
// ? createMemoryHistory
// : (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory);
// export const Router = createRouter({
// scrollBehavior: () => ({ left: 0, top: 0 }),
// routes,
// // Leave this as is and make changes in quasar.conf.js instead!
// // quasar.conf.js -> build -> vueRouterMode
// // quasar.conf.js -> build -> publicPath
// history: createHistory(process.env.VUE_ROUTER_BASE),
// });
// export default route(function (/* { store, ssrContext } */) {
// return Router;
// });

View File

@@ -1,6 +1,17 @@
import { RouteRecordRaw } from 'vue-router';
const routes: RouteRecordRaw[] = [
{
path: '/login',
component: () => import('pages/LoginPage.vue')
},
{
path:'/FlowChart',
component:()=>import('layouts/MainLayout.vue'),
children:[
{path:'',component:()=>import('pages/FlowChart.vue')}
]
},
{
path: '/',
component: () => import('layouts/MainLayout.vue'),
@@ -9,11 +20,13 @@ const routes: RouteRecordRaw[] = [
{ path: 'ruleEditor', component: () => import('pages/RuleEditor.vue') },
{ path: 'test', component: () => import('pages/testQursar.vue') },
{ path: 'flow', component: () => import('pages/testFlow.vue') },
{ path: 'flowchart', component: () => import('pages/FlowChartTest.vue') },
{ path: 'FlowChartTest', component: () => import('pages/FlowChartTest.vue') },
{ path: 'flowEditor', component: () => import('pages/FlowEditorPage.vue') },
{ path: 'flowEditor2', component: () => import('pages/FlowChart.vue') },
{ path: 'flowChart2', component: () => import('pages/FlowEditorPage2.vue') },
// { path: 'FlowChart', component: () => import('pages/FlowChart.vue') },
{ path: 'right', component: () => import('pages/testRight.vue') },
{ path: 'domain', component: () => import('pages/TenantDomain.vue') },
{ path: 'userdomain', component: () => import('pages/UserDomain.vue')},
{ path: 'condition', component: () => import('pages/conditionPage.vue') }
],
},
// Always leave this as last one,

View File

@@ -1,71 +1,142 @@
import { defineStore } from 'pinia';
import { AppInfo ,IActionFlow} from 'src/types/ActionTypes';
import { kintoneEvents,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;
eventTree:KintoneEventManager;
selectedEvent:IKintoneEvent|undefined;
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();
export const useFlowEditorStore = defineStore("flowEditor",{
state: ():FlowEditorState => ({
flowNames1: '',
appInfo:undefined,
flows:[],
selectedFlow:undefined,
eventTree:kintoneEvents,
selectedEvent:undefined
}),
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: [],
}),
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;
},
setApp(app:AppInfo){
this.appInfo=app;
setActiveNode(node: IActionNode) {
this.activeNode = node;
},
async setFlow(){
if(this.appInfo===undefined) return;
const actionFlows = await flowCtrl.getFlows(this.appInfo?.appId);
//eventTreeにバンドする
this.eventTree.bindFlows(actionFlows);
if(actionFlows && actionFlows.length>0){
this.setFlows(actionFlows);
}
if(actionFlows && actionFlows.length==1){
this.selectFlow(actionFlows[0]);
}
}
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;
},
/**
* フローをDBに保存及び更新する
*/
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),
};
}
if (isNew) {
return await flowCtrl.SaveFlow(jsonData);
} else {
return await flowCtrl.UpdateFlow(jsonData);
}
},
async deleteEvent(event: IKintoneEvent) {
const store = useFlowEditorStore();
if (event.flowData) {
const flow = event.flowData;
if (flow.id !== '') {
await flowCtrl.DeleteFlow(flow.id)
if (this.flows) {
this.flows = this.flows.filter((f) => f.id !== flow.id);
}
}
eventTree.deleteEvent(event, store);
}
else {
eventTree.deleteEvent(event, store);
}
},
/**
* デプロイする
*/
async deploy(): Promise<boolean> {
if (this.appInfo === undefined) {
return false;
}
return await flowCtrl.depoly(this.appInfo?.appId);
},
},
});

View File

@@ -0,0 +1,91 @@
import { defineStore } from 'pinia';
import { api } from 'boot/axios';
import {router} from 'src/router';
import {IDomainInfo} from '../types/ActionTypes';
export interface IUserState{
token?:string;
returnUrl:string;
currentDomain:IDomainInfo;
LeftDrawer:boolean;
}
export const useAuthStore = defineStore({
id: 'auth',
state: ():IUserState =>{
const token=localStorage.getItem('token')||'';
if(token!==''){
api.defaults.headers["Authorization"]='Bearer ' + token;
}
return {
token,
returnUrl: '',
LeftDrawer:false,
currentDomain: JSON.parse(localStorage.getItem('currentDomain')||"{}")
}
},
getters:{
toggleLeftDrawer():boolean{
return this.LeftDrawer;
}
},
actions: {
toggleLeftMenu(){
this.LeftDrawer=!this.LeftDrawer;
},
async login(username:string, password:string) {
const params = new URLSearchParams();
params.append('username', username);
params.append('password', password);
try{
const result = await api.post(`api/token`,params);
console.info(result);
this.token =result.data.access_token;
localStorage.setItem('token', result.data.access_token);
api.defaults.headers["Authorization"]='Bearer ' + this.token;
this.currentDomain=await this.getCurrentDomain();
localStorage.setItem('currentDomain',JSON.stringify(this.currentDomain));
this.router.push(this.returnUrl || '/');
return true;
}catch(e)
{
console.info(e);
return false;
}
},
async getCurrentDomain():Promise<IDomainInfo>{
const activedomain = await api.get(`api/activedomain`);
return {
id:activedomain.data.id,
domainName:activedomain.data.name,
kintoneUrl:activedomain.data.url
}
},
async getUserDomains():Promise<IDomainInfo[]>{
const resp = await api.get(`api/domain`);
const domains =resp.data as any[];
return domains.map(data=>{
return {
id:data.id,
domainName:data.name,
kintoneUrl:data.url
}
});
},
logout() {
this.token = undefined;
localStorage.removeItem('token');
localStorage.removeItem('currentDomain');
router.push('/login');
},
async setCurrentDomain(domain:IDomainInfo){
if(domain.id===this.currentDomain.id){
return;
}
await api.put(`api/activedomain/${domain.id}`);
this.currentDomain=domain;
localStorage.setItem('currentDomain',JSON.stringify(this.currentDomain));
}
}
});

View File

@@ -1,12 +1,35 @@
import { v4 as uuidv4 } from 'uuid';
export interface IDomainInfo{
id:number;
domainName:string;
kintoneUrl:string;
}
/**
* アプリ情報
*/
export interface AppInfo {
appId:string;
code?:string;
name:string;
description?:string;
appId: string;
code?: string;
name: string;
description?: string;
}
/**
* 属性項目情報
*/
export interface IProp{
//プロパティ名
name: string;
//プロパティ表示名
displayName: string;
placeholder: string;
//入力提示・説明
hint:string;
//関連属性リスト
connectProps:[{key:string,propName:string}]|undefined;
//プロパティ設定値
modelValue: any;
}
/**
@@ -14,34 +37,44 @@ export interface AppInfo {
*/
export interface IActionProperty {
component: string;
props: {
//プロパティ名
name: string;
//プロパティ表示名
props: IProp;
}
/**
* 変数オブジェクト
*/
export interface IActionVariable{
actionName:string;
displayName:string;
name: {
name:string;
actionName:string;
displayName:string;
placeholder: string;
//プロパティ設定値
modelValue: any;
vars : {
vName:string;
logicalOperator:string;
field: object;
}[]
};
}
/**
* アクションタイプ定義
*/
export interface IActionNode{
id:string;
export interface IActionNode {
id: string;
//アクション名
name:string;
title:string;
subTitle:string;
inputPoint:string;
name: string;
title: string;
varName:IProp|undefined;
subTitle: string;
inputPoint: string;
//出力ポイント(条件分岐以外未使用)
outputPoints:Array<string>;
outputPoints: Array<string>;
//ルートアクションKintone event
isRoot:boolean;
isRoot: boolean;
//アクションのプロパティ定義
actionProps:Array<IActionProperty>;
actionProps: Array<IActionProperty>;
//アクションのプロパティ設定値抽出
ActionValue:object
ActionValue: object
prevNodeId?: string;
nextNodeIds: Map<string, string>;
}
@@ -49,9 +82,17 @@ export interface IActionNode{
* アクションフローの定義
*/
export interface IActionFlow {
id:string;
actionNodes:Array<IActionNode>;
getRoot():IActionNode|undefined;
id: string;
actionNodes: IActionNode[];
addNode(newNode: IActionNode, prevNode?: IActionNode, inputPoint?: string): IActionNode;
removeNode(targetNode: IActionNode): boolean;
removeAllNext(targetNodeId: string): void;
getVarNames(currentNode:IActionNode):IActionVariable[];
findNodeById(id: string): IActionNode | undefined;
toJSON(): any;
getRoot(): IActionNode | undefined;
createNewId(): string;
getColumns(node:IActionNode):number;
}
/**
@@ -59,18 +100,10 @@ export interface IActionFlow {
*/
class ActionProperty implements IActionProperty {
component: string;
props: {
// プロパティ名
name: string;
// プロパティ表示名
displayName: string;
placeholder: string;
// プロパティ設定値
modelValue: any;
};
props: IProp;
static defaultProperty():IActionProperty{
return new ActionProperty('InputText','displayName','表示名','表示を入力してください','');
static defaultProperty(): IActionProperty {
return new ActionProperty('InputText', 'displayName', '表示名', '表示を入力してください', '','');
};
constructor(
@@ -78,6 +111,7 @@ class ActionProperty implements IActionProperty {
name: string,
displayName: string,
placeholder: string,
hint:string,
modelValue: any
) {
this.component = component;
@@ -85,6 +119,8 @@ class ActionProperty implements IActionProperty {
name: name,
displayName: displayName,
placeholder: placeholder,
hint:hint,
connectProps:undefined,
modelValue: modelValue
};
}
@@ -95,23 +131,33 @@ class ActionProperty implements IActionProperty {
* IActionNodeの実装、RootActionNode以外のアクション定義
*/
export class ActionNode implements IActionNode {
id:string;
id: string;
name: string;
title:string;
get subTitle():string{
get title(): string {
const prop = this.actionProps.find((prop) => prop.props.name === "displayName");
return prop?.props.modelValue;
};
get subTitle(): string {
return this.name;
};
inputPoint:string;
//変数名
get varName():IProp|undefined{
const prop = this.actionProps.find((prop) => prop.props.name === "verName");
return prop?.props;
}
inputPoint: string;
//出力ポイント(条件分岐以外未使用)
outputPoints:Array<string>;
outputPoints: Array<string>;
actionProps: Array<IActionProperty>;
get isRoot(): boolean{
get isRoot(): boolean {
return false;
};
get ActionValue():object{
const propValue:any={};
this.actionProps.forEach((value)=>{
propValue[value.props.name]=value.props.modelValue
get ActionValue(): object {
const propValue: any = {};
this.actionProps.forEach((value) => {
propValue[value.props.name] = value.props.modelValue
});
return propValue;
};
@@ -119,24 +165,23 @@ export class ActionNode implements IActionNode {
nextNodeIds: Map<string, string>;
constructor(
name: string,
title:string,
inputPoint:string,
title: string,
inputPoint: string,
outputPoint: Array<string> = [],
actionProps: Array<IActionProperty> =[ActionProperty.defaultProperty()],
actionProps: Array<IActionProperty> = [ActionProperty.defaultProperty()],
) {
this.id=uuidv4();
this.id = uuidv4();
this.name = name;
this.title=title;
this.inputPoint=inputPoint;
this.inputPoint = inputPoint;
this.outputPoints = outputPoint;
const defProp =ActionProperty.defaultProperty();
defProp.props.displayName=title;
this.actionProps =actionProps;
const prop = this.actionProps.find((prop)=>prop.props.name===defProp.props.name);
if(prop===undefined){
const defProp = ActionProperty.defaultProperty();
defProp.props.modelValue = title;
this.actionProps = actionProps;
const prop = this.actionProps.find((prop) => prop.props.name === defProp.props.name);
if (prop === undefined) {
this.actionProps.unshift(defProp);
}
this.nextNodeIds=new Map<string,string>();
this.nextNodeIds = new Map<string, string>();
}
}
@@ -144,33 +189,34 @@ export class ActionNode implements IActionNode {
* ルートアクション定義
*/
export class RootAction implements IActionNode {
id:string;
id: string;
name: string;
title:string;
subTitle:string;
inputPoint:string;
title: string;
subTitle: string;
inputPoint: string;
varName: IProp | undefined=undefined;
//出力ポイント(条件分岐以外未使用)
outputPoints:Array<string>;
outputPoints: Array<string>;
isRoot: boolean;
actionProps: Array<IActionProperty>;
ActionValue:object;
ActionValue: object;
prevNodeId?: string = undefined;
nextNodeIds: Map<string, string>;
constructor(
name: string,
title:string,
subTitle:string,
title: string,
subTitle: string,
) {
this.id=uuidv4();
this.id = uuidv4();
this.name = name;
this.title=title;
this.subTitle=subTitle;
this.inputPoint='';
this.title = title;
this.subTitle = subTitle;
this.inputPoint = '';
this.outputPoints = [];
this.isRoot = true;
this.actionProps=[];
this.ActionValue={};
this.nextNodeIds=new Map<string,string>();
this.actionProps = [];
this.ActionValue = {};
this.nextNodeIds = new Map<string, string>();
}
}
@@ -178,15 +224,16 @@ export class RootAction implements IActionNode {
* アクションフローの定義
*/
export class ActionFlow implements IActionFlow {
id:string;
actionNodes:Array<IActionNode>;
constructor(actionNodes:Array<IActionNode>|RootAction){
if(actionNodes instanceof Array){
this.actionNodes=actionNodes;
}else{
this.actionNodes=[actionNodes];
id: string;
actionNodes: Array<IActionNode>;
constructor(actionNodes: Array<IActionNode> | RootAction) {
if (actionNodes instanceof Array) {
this.actionNodes = actionNodes;
} else {
this.actionNodes = [actionNodes];
}
this.id=uuidv4();
this.id = '';
//this.id = uuidv4();
}
/**
* ノードを追加する
@@ -198,23 +245,22 @@ export class ActionFlow implements IActionFlow {
* @returns 追加されたノード
*/
addNode(
newNode:IActionNode,
prevNode?:IActionNode,
inputPoint?:string):IActionNode
{
if(inputPoint!==undefined){
newNode.inputPoint=inputPoint;
newNode: IActionNode,
prevNode?: IActionNode,
inputPoint?: string): IActionNode {
if (inputPoint !== undefined) {
newNode.inputPoint = inputPoint;
}
if(prevNode!==undefined){
this.connectNodes(prevNode,newNode,inputPoint||'');
}else{
prevNode=this.actionNodes[this.actionNodes.length-1];
this.connectNodes(prevNode,newNode,inputPoint||'');
if (prevNode !== undefined) {
this.resetNodeRelation(prevNode, newNode, inputPoint || '');
} else {
prevNode = this.actionNodes[this.actionNodes.length - 1];
this.connectNodes(prevNode, newNode, inputPoint || '');
}
const index=this.actionNodes.findIndex(node=>node.id===prevNode?.id)
if(index>=0){
this.actionNodes.splice(index+1,0,newNode);
}else{
const index = this.actionNodes.findIndex(node => node.id === prevNode?.id)
if (index >= 0) {
this.actionNodes.splice(index + 1, 0, newNode);
} else {
this.actionNodes.push(newNode);
}
return newNode;
@@ -223,124 +269,148 @@ export class ActionFlow implements IActionFlow {
* ノードを削除する
* @param delNode
*/
removeNode(targetNode :IActionNode):boolean{
if (!targetNode ) {
removeNode(targetNode: IActionNode): boolean {
if (!targetNode) {
return false;
}
if(targetNode.isRoot){
if (targetNode.isRoot) {
return false;
}
this.disconnectFromPrevNode(targetNode);
this.reconnectOrRemoveNextNodes(targetNode);
this.removeFromActionNodes(targetNode.id);
return true;
}
/***
* 目標ノードの次のノードを全部削除する
*/
removeAllNext(targetNodeId :string){
if (!targetNodeId || targetNodeId==='') {
return false;
}
const targetNode=this.findNodeById(targetNodeId);
if(!targetNode){
return false;
/***
* 目標ノードの次のノードを全部削除する
*/
removeAllNext(targetNodeId: string) {
if (!targetNodeId || targetNodeId === '') {
return false;
}
const targetNode = this.findNodeById(targetNodeId);
if (!targetNode) {
return false;
}
if (targetNode.nextNodeIds.size == 0) {
return false;
}
for (const [, id] of targetNode.nextNodeIds) {
this.removeAll(id);
}
targetNode.nextNodeIds.clear();
}
if(targetNode.nextNodeIds.size==0){
return false;
}
for (const [, id] of targetNode.nextNodeIds) {
this.removeAllNext(id);
this.removeFromActionNodes(id);
}
}
// 断开与前一个节点的连接
disconnectFromPrevNode(targetNode: IActionNode): void {
const prevNodeId = targetNode.prevNodeId;
if (prevNodeId) {
const prevNode = this.findNodeById(prevNodeId);
if (prevNode) {
for (const [key, value] of prevNode.nextNodeIds) {
if (value === targetNode.id) {
prevNode.nextNodeIds.delete(key);
/***
* 目標ノードの次のノードを全部削除する
*/
removeAll(targetNodeId: string) {
if (!targetNodeId || targetNodeId === '') {
return;
}
const targetNode = this.findNodeById(targetNodeId);
if (!targetNode) {
return
}
if (targetNode.nextNodeIds.size == 0) {
return
}
for (const [, id] of targetNode.nextNodeIds) {
this.removeAll(id);
}
this.removeNode(targetNode);
}
// 断开与前一个节点的连接
disconnectFromPrevNode(targetNode: IActionNode): void {
const prevNodeId = targetNode.prevNodeId;
if (prevNodeId) {
const prevNode = this.findNodeById(prevNodeId);
if (prevNode) {
for (const [key, value] of prevNode.nextNodeIds) {
if (value === targetNode.id) {
prevNode.nextNodeIds.delete(key);
}
}
}
}
}
}
// actionNodes 数组中移除节点
private removeFromActionNodes(targetNodeId: string): void {
const index = this.actionNodes.findIndex(node => node.id === targetNodeId);
if (index > -1) {
this.actionNodes.splice(index, 1);
}
}
/**
* ノード削除時、前のノードと次のノードを接続する
* @param targetNode
*/
reconnectOrRemoveNextNodes(targetNode: IActionNode): void {
if(!targetNode || !targetNode.prevNodeId ){
return;
}
//前のノードを取得
const prevNode = this.findNodeById(targetNode.prevNodeId);
if(!prevNode) return;
//次のノード取得
const nextNodeIds = targetNode.nextNodeIds;
if(nextNodeIds.size==0){
return;
}
//次のノード一つの場合
if(nextNodeIds.size==1){
const nextNodeId = nextNodeIds.get('');
if(!nextNodeId) return;
const nextNode = this.findNodeById(nextNodeId) ;
if(!nextNode) return;
nextNode.prevNodeId=prevNode.id;
prevNode.nextNodeIds.set(targetNode.inputPoint||'',nextNodeId);
return;
}
//二つ以上の場合
for(const [point,nextid] of nextNodeIds){
const nextNode = this.findNodeById(nextid);
if(!nextNode) return;
if(!this.connectNodes(prevNode,nextNode,point)){
this.removeAllNext(nextid);
this.removeFromActionNodes(nextid);
// actionNodes からノードを削除する
private removeFromActionNodes(targetNodeId: string): void {
const index = this.actionNodes.findIndex(node => node.id === targetNodeId);
if (index > -1) {
this.actionNodes.splice(index, 1);
}
}
}
/**
* 二つノードを接続する
* @param prevNode
* @param nextNodeId
* @param point
* @returns
*/
connectNodes(prevNode:IActionNode,nextNode:IActionNode,point:string):boolean{
if(!prevNode || !nextNode){
return false;
/**
* ノード削除時、前のノードと次のノードを接続する
* @param targetNode
*/
reconnectOrRemoveNextNodes(targetNode: IActionNode): void {
if (!targetNode || !targetNode.prevNodeId) {
return;
}
//前のノードを取得
const prevNode = this.findNodeById(targetNode.prevNodeId);
if (!prevNode) return;
//次のノード取得
const nextNodeIds = targetNode.nextNodeIds;
if (nextNodeIds.size == 0) {
return;
}
//次のノード一つの場合
if (nextNodeIds.size == 1) {
const nextNodeId = nextNodeIds.get('');
if (!nextNodeId) return;
const nextNode = this.findNodeById(nextNodeId);
if (!nextNode) return;
this.connectNodes(prevNode,nextNode,targetNode.inputPoint || '');
return;
}
//二つ以上の場合
for (const [point, nextid] of nextNodeIds) {
const nextNode = this.findNodeById(nextid);
if (!nextNode) return;
if (!this.connectNodes(prevNode, nextNode, point)) {
this.removeAllNext(nextid);
this.removeFromActionNodes(nextid);
}
if(!nextNode) return false;
prevNode.nextNodeIds.set(point,nextNode.id);
nextNode.prevNodeId=prevNode.id;
nextNode.inputPoint=point;
return true;
}
}
/**
* 二つノードを接続する
* @param prevNode
* @param nextNodeId
* @param point
* @returns
*/
connectNodes(prevNode: IActionNode, nextNode: IActionNode, point: string): boolean {
if (!prevNode || !nextNode) {
return false;
}
if (!nextNode) return false;
prevNode.nextNodeIds.set(point, nextNode.id);
nextNode.prevNodeId = prevNode.id;
nextNode.inputPoint = point;
return true;
}
/**
*
* @param prevNode ノード挿入時の接続をリセットする
* @param newNode
* @param inputPoint
*/
resetNodeRelation(prevNode: IActionNode, newNode: IActionNode, inputPoint?: string) {
// 设置新节点和前节点的关联
//元の次のノードを取得
const originalNextNodeId = prevNode.nextNodeIds.get(inputPoint || '');
prevNode.nextNodeIds.set(inputPoint || '', newNode.id);
newNode.prevNodeId = prevNode.id;
// 保存前节点原有的后节点ID
const originalNextNodeId = prevNode.nextNodeIds.get(inputPoint || '');
this.setNewNodeNextId(newNode,originalNextNodeId,inputPoint);
newNode.inputPoint=inputPoint||'';
this.setNewNodeNextId(newNode, originalNextNodeId, inputPoint);
}
/**
@@ -350,16 +420,19 @@ reconnectOrRemoveNextNodes(targetNode: IActionNode): void {
* @param inputPoint
*/
private setNewNodeNextId(newNode: IActionNode, originalNextNodeId: string | undefined, inputPoint?: string) {
// 如果原先的后节点存在
if (originalNextNodeId) {
// 检查新节点的 outputPoints 是否包含该 inputPoint
if (newNode.outputPoints.includes(inputPoint || '')) {
newNode.nextNodeIds.set(inputPoint || '', originalNextNodeId);
} else {
// 如果不包含,选择新节点的一个 outputPoint
const alternativeOutputPoint = newNode.outputPoints.length > 0 ? newNode.outputPoints[0] : '';
newNode.nextNodeIds.set(alternativeOutputPoint, originalNextNodeId);
}
// 元の接続ノードが存在する
if (!originalNextNodeId) {return;}
const originNextNode = this.findNodeById(originalNextNodeId);
if (!originNextNode) {return;}
// 新しいノードの outputPoints に該当 inputPointが存在するか場合をチェックする
if (newNode.outputPoints.includes(inputPoint || '')) {
newNode.nextNodeIds.set(inputPoint || '', originalNextNodeId);
originNextNode.prevNodeId=newNode.id;
} else {
// inputPointが存在しない場合、outputPointのポイントの任意ポートを選択する
const alternativeOutputPoint = newNode.outputPoints.length > 0 ? newNode.outputPoints[0] : '';
newNode.nextNodeIds.set(alternativeOutputPoint, originalNextNodeId);
originNextNode.prevNodeId=newNode.id;
}
}
@@ -370,39 +443,93 @@ reconnectOrRemoveNextNodes(targetNode: IActionNode): void {
return this.actionNodes.find((node) => node.id === id);
}
toJSON() {
getVarNames(currentNode:IActionNode):IActionVariable[]{
let varNames:IActionVariable[]=[];
if(currentNode.prevNodeId!==undefined){
const prevNode=this.findNodeById(currentNode.prevNodeId);
if(prevNode!==undefined){
varNames = this.getPrevVarNames(prevNode);
}
}
return varNames;
}
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,
name:prevNode.varName.modelValue
});
}
if(prevNode.prevNodeId!==undefined){
const prevPrevNode=this.findNodeById(prevNode.prevNodeId);
if(prevPrevNode!==undefined){
const prevVars = this.getPrevVarNames(prevPrevNode);
varNames=[...prevVars,...varNames];
}
}
return varNames;
}
toJSON() {
return {
id:this.id,
id: this.id,
actionNodes: this.actionNodes.map(node => {
const { nextNodeIds, ...rest } = node;
return {
...rest,
nextNodeIds: Array.from(nextNodeIds.entries())
nextNodeIds: Object.fromEntries(nextNodeIds)
};
})
};
}
getRoot():IActionNode|undefined{
return this.actionNodes.find(node=>node.isRoot)
getColumns(node:IActionNode):number{
let result= 1;
if(node.outputPoints && node.outputPoints.length>1){
result += node.outputPoints.length -1;
}
let nextNode;
for (const [key, id] of node.nextNodeIds) {
nextNode=this.findNodeById(id);
if(nextNode){
result +=this.getColumns(nextNode)-1;
}
}
return result;
}
getRoot(): IActionNode | undefined {
return this.actionNodes.find(node => node.isRoot)
}
createNewId():string{
this.id=uuidv4();
return this.id;
}
static fromJSON(json: string): ActionFlow {
const parsedObject = JSON.parse(json);
const actionNodes = parsedObject.actionNodes.map((node: any) => {
const nodeClass = !node.isRoot? new ActionNode(node.name,node.title,node.inputPoint,node.outputPoint,node.actionProps)
:new RootAction(node.name,node.title,node.subTitle);
nodeClass.nextNodeIds=new Map(node.nextNodeIds);
nodeClass.prevNodeId=node.prevNodeId;
nodeClass.id=node.id;
const nodeClass = !node.isRoot ? new ActionNode(node.name, node.title, node.inputPoint, node.outputPoints, node.actionProps)
: new RootAction(node.name, node.title, node.subTitle);
nodeClass.nextNodeIds = new Map<string,string>(Object.entries(node.nextNodeIds));
nodeClass.prevNodeId = node.prevNodeId;
nodeClass.id = node.id;
return nodeClass;
});
const actionFlow = new ActionFlow(actionNodes);
actionFlow.id=parsedObject.id;
actionFlow.id = parsedObject.id;
return actionFlow;
}
}

View File

@@ -0,0 +1,383 @@
//ノード種別
export enum NodeType{
Root = 'root',
LogicGroup ='logicgroup',
Condition ='condition'
}
//ロジックオペレーター
export enum LogicalOperator{
AND = 'AND',
OR = 'OR'
}
//条件オペレーター
export enum Operator{
Equal = '=',
NotEqual='!=',
Greater = '>',
GreaterOrEqual = '>=',
Less = '<',
LessOrEqual = '<=',
Contains = 'contains',
NotContains = 'not contains',
StartWith = 'start With',
EndWith = 'end with',
NotStartWith = 'not start with',
NotEndWith = 'not end with'
}
// INode
export interface INode {
index:number;
type: NodeType;
header:string;
parent: INode | null;
logicalOperator:LogicalOperator
}
// ロジックノード
export class GroupNode implements INode {
index:number;
type: NodeType;
children: INode[];
parent: INode | null;
logicalOperator: LogicalOperator;
get label():string{
return this.logicalOperator;
}
get header():string{
return this.type===NodeType.Root?'root':'generic';
}
get expanded():boolean{
return this.children.length>0;
}
constructor(logicOp:LogicalOperator, parent: INode | null) {
this.index=0;
this.type = parent==null?NodeType.Root: NodeType.LogicGroup;
this.logicalOperator = logicOp;
this.parent=parent;
this.children=[];
}
static fromJSON(json: any, parent: INode | null = null): GroupNode {
const node = new GroupNode(json.logicalOperator, parent);
node.index=json.index;
node.children = json.children.map((childJson: any) => {
return childJson.type === NodeType.LogicGroup
? GroupNode.fromJSON(childJson, node)
: ConditionNode.fromJSON(childJson, node);
});
return node;
}
}
export type OperatorListItem = {
label: string;
value: string;
}
// 条件式ノード
export class ConditionNode implements INode {
index: number;
type: NodeType;
parent:INode;
get logicalOperator(): LogicalOperator{
return this.parent.logicalOperator;
};
object: any; // 比較元
operator: Operator | OperatorListItem; // 比較子
value: any;
get header():string{
return 'generic';
}
constructor(object: any, operator: Operator | OperatorListItem, value: any, parent: GroupNode) {
this.index=0;
this.type = NodeType.Condition;
this.object = object;
this.operator = operator;
this.value = value;
this.parent=parent;
}
static fromJSON(json: any, parent: GroupNode): ConditionNode {
const node= new ConditionNode(
json.object,
json.operator,
json.value,
parent
);
node.index=json.index;
return node;
}
}
// 条件式の管理クラス
export class ConditionTree {
root: GroupNode;
maxIndex:number;
queryString:string;
constructor() {
this.maxIndex=0;
this.root = new GroupNode(LogicalOperator.AND, null);
this.queryString='';
}
// ノード追加
addNode(parent: GroupNode, node: INode): void {
this.maxIndex++;
node.index=this.maxIndex;
parent.children.push(node);
}
// ノード削除
removeNode(node: INode): void {
if (node.parent === null) {
throw new Error('ルートノード削除できません');
} else {
const parent = node.parent as GroupNode;
const index = parent.children.indexOf(node);
if (index > -1) {
parent.children.splice(index, 1);
}
}
}
findByIndex(index:number):INode|undefined{
return this.findChildren(this.root,index);
}
findChildren(parent: GroupNode, index: number): INode | undefined {
if (parent.index === index) {
return parent;
}
for (const node of parent.children) {
if (node.index === index) {
return node;
}
if (node.type !== NodeType.Condition) {
const foundNode = this.findChildren(node as GroupNode, index);
if (foundNode) {
return foundNode;
}
}
}
return undefined;
}
getMaxIndex(node:INode):number{
let maxIndex:number=node.index;
if(node.type!==NodeType.Condition){
const groupNode = node as GroupNode;
groupNode.children.forEach((child)=>{
const childMax = this.getMaxIndex(child);
if(childMax>maxIndex){
maxIndex=childMax;
}
});
}
return maxIndex;
}
//条件式を表示する
buildConditionString(node:INode){
if (node.type !== NodeType.Condition) {
let conditionString = '(';
const groupNode = node as GroupNode;
for (let i = 0; i < groupNode.children.length; i++) {
const childConditionString = this.buildConditionString(groupNode.children[i]);
if (childConditionString !== '') {
conditionString += childConditionString;
if (i < groupNode.children.length - 1) {
conditionString += ` ${groupNode.logicalOperator} `;
}
}
}
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.sharedText} ${typeof condNode.operator === 'object' ? condNode.operator.label : condNode.operator} ${condNode.value.sharedText}`;
// 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 ) {
if (!condNode.object.code || !condNode.value.sharedText){
return '';
}
return `${condNode.object.code} ${typeof condNode.operator === 'object' ? condNode.operator.value : condNode.operator} "${condNode.value.sharedText}"`;
} else {
return '';
}
}
}
/**
*
* @param node ノード移動
* @param direction
* @returns
*/
moveNode(node:INode, direction: 'up' | 'down'): void {
if (!node || !node.parent) {
return;
}
const parent = node.parent as GroupNode;
const currentIndex = parent.children.findIndex(child => child === node);
if (currentIndex === -1) {
return;
}
const newIndex = direction === 'up' ? currentIndex - 1 : currentIndex + 1;
// 範囲外のインデックスの処理
if (newIndex >= 0 && newIndex < parent.children.length) {
// ノードの位置を入れ替える
[parent.children[currentIndex], parent.children[newIndex]] = [parent.children[newIndex], parent.children[currentIndex]];
return; // 範囲外なら移動しない
}else if(newIndex<0 && parent.parent){
this.removeNode(node);
const parentIndex = (parent.parent as GroupNode).children.findIndex(child => child === parent);
(parent.parent as GroupNode).children.splice(parentIndex, 0, node);
node.parent = parent.parent;
}
}
getGroups(parent:GroupNode):number[]{
const groups:number[]=[];
groups.push(parent.index);
parent.children.forEach((node)=>{
if(node.type!==NodeType.Condition){
groups.push(...this.getGroups(node as GroupNode));
}
});
return groups;
}
/**
* 条件ノードをグループ化
* @param nodes 結合ノードを選択する
* @param logicOp
* @returns
*/
createGroupNode(firstNode:INode,nodes: INode[], logicOp: LogicalOperator): GroupNode | null {
if (nodes.length === 0) {
return null;
}
// 最初のノードの親ノードを取得
const parent = firstNode.parent as GroupNode;
if (!parent) {
throw new Error('ルートノードをグループ化できません');
}
// 親ノードを取得
const filteredNodes = nodes.filter(node => node.parent === parent);
// 新しいグループノードを作成
const newGroup = new GroupNode(logicOp, parent);
this.maxIndex++;
newGroup.index = this.maxIndex;
// 新しいグループノードの挿入位置を取得
let firstNodeIndex = parent.children.length;
if (filteredNodes.length > 0) {
firstNodeIndex = parent.children.indexOf(filteredNodes[0]);
}
filteredNodes.forEach(node => {
// 元の親ノードから削除する
const nodeIndex = parent.children.indexOf(node);
parent.children.splice(nodeIndex, 1);
// 新しいグループに追加
node.parent = newGroup;
newGroup.children.push(node);
});
// 新しいGroupNodeを挿入する
parent.children.splice(firstNodeIndex, 0, newGroup);
return newGroup;
}
/**
* GroupNodeを解散する
* @param groupNode 対象グループノード
*/
dissolveGroupNode(groupNode: GroupNode): void {
if (groupNode.parent === null || groupNode.type !== NodeType.LogicGroup) {
throw new Error('ルートノードと非グループノードを解散することができません');
}
// 親ノードを取得
const parent = groupNode.parent as GroupNode;
const groupIndex = parent.children.indexOf(groupNode);
// 子ノードをリセットする
groupNode.children.forEach(child => {
child.parent = parent;
parent.children.splice(groupIndex, 0, child);
});
//グループノードを削除する
parent.children.splice(groupIndex + groupNode.children.length, 1);
}
// Jsonから復元
fromJson(jsonString: string): INode {
const json = JSON.parse(jsonString);
this.root = GroupNode.fromJSON(json) as GroupNode;
this.maxIndex=this.getMaxIndex(this.root);
return this.root;
}
toJson():string{
return JSON.stringify({queryString :this.queryString, ...this.root}, (key, value) => {
if (key === 'parent') {
return value ? value.type : null;
}
return value;
});
}
setQuery(queryString:string){
this.queryString=queryString;
}
}

View File

@@ -1,112 +1,338 @@
import { publicDecrypt } from 'crypto';
import {IActionFlow} from './ActionTypes';
export interface TreeNode {
import { useFlowEditorStore } from 'src/stores/flowEditor';
import { IActionFlow } from './ActionTypes';
export interface IKintoneEventNode {
label: string;
header: string;
eventId: string;
parentId: string;
}
export interface IKintoneEvent extends TreeNode {
eventId: string;
export interface IKintoneEvent extends IKintoneEventNode {
hasFlow: boolean;
flowData?: IActionFlow;
}
export interface IKintoneScreen extends TreeNode {
label: string;
events: IKintoneEvent[];
export interface IKintoneEventGroup extends IKintoneEventNode {
events: IKintoneEventNode[];
}
export class kintoneEvent implements IKintoneEvent{
export class kintoneEvent implements IKintoneEvent {
eventId: 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;
constructor({eventId,label}:{eventId:string,label:string}){
this.eventId=eventId;
this.label=label;
header = 'EVENT';
constructor(label: string, eventId: string, parentId: string) {
this.eventId = eventId;
this.label = label;
this.parentId = parentId;
}
}
export class kintoneEventGroup implements IKintoneEventGroup {
eventId: string;
parentId: string;
label: string;
events: IKintoneEventNode[];
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;
}
}
export class kintoneEventForChange implements IKintoneEventGroup {
eventId: string;
parentId: string;
label: string;
events: IKintoneEventNode[];
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;
}
}
export class KintoneEventManager {
public screens: IKintoneScreen[];
public screens: IKintoneEventGroup[];
constructor(screens: IKintoneScreen[]) {
this.screens = screens;
constructor() {
this.screens = this.getKintoneEvents();
}
public bindFlows(flows:IActionFlow[]){
for (const screen of this.screens) {
screen.events.forEach((ev)=>ev.flowData=undefined);
}
for (const flow of flows){
const eventId =flow.getRoot()?.name;
if(eventId!==undefined){
const event = this.findEventById(eventId);
if(event!==null){
event.flowData=flow;
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 {
//EventGroupのIDを取得
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 = {
label: flow.getRoot()?.subTitle || '',
eventId: eventId,
parentId: groupId,
header: 'DELETABLE',
hasFlow: true,
flowData: flow,
};
groupEvent.events.push(newEvent);
}
}
}
}
}
public findEventById(eventId: string): IKintoneEvent | null {
public findEventById(eventId: string): IKintoneEventNode | null {
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;
}
}
}
}
return null;
}
public findScreen(eventId:string):IKintoneScreen|null{
for (const screen of this.screens) {
if(screen.events.some((ev:IKintoneEvent)=>ev.eventId===eventId)){
return screen;
}
public findScreen(eventId: string): IKintoneEventGroup | undefined {
return this.screens.find((screen) => screen.eventId == eventId);
}
public deleteEvent(
event: kintoneEvent,
store: ReturnType<typeof useFlowEditorStore>
) {
if (event.header !== 'DELETABLE') {
return;
}
return null;
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'
),
],
''
),
];
}
}
export const kintoneEvents:KintoneEventManager = new KintoneEventManager([
{
label:"レコード追加画面",
events:[
new kintoneEvent({label:"レコード追加画面を表示した後",eventId:"app.record.create.show"}),
new kintoneEvent({label:"保存をクリックしたとき",eventId:"app.record.create.submit"}),
new kintoneEvent({label:"保存が成功したとき",eventId:"app.record.create.submit.success"}),
new kintoneEvent({label:"フィールドの値を変更したとき",eventId:"app.record.create.change"}),
]
},
{
label:"レコード詳細画面",
events:[
new kintoneEvent({label:"レコード詳細画面を表示した後",eventId:"app.record.detail.show"}),
new kintoneEvent({label:"レコードを削除するとき",eventId:"app.record.detail.delete.submit"}),
new kintoneEvent({label:"プロセス管理のアクションを実行したとき",eventId:"app.record.detail.process.proceed"}),
]
},
{
label:"レコード編集画面",
events:[new kintoneEvent({label:"レコード編集画面を表示した後",eventId:"app.record.edit.show"}),
new kintoneEvent({label:"保存をクリックしたとき",eventId:"app.record.edit.submit"}),
new kintoneEvent({label:"保存が成功したとき",eventId:"app.record.edit.submit.success"}),
new kintoneEvent({label:"フィールドの値を変更したとき",eventId:"app.record.edit.change"}),
]
},
{
label:"レコード一覧画面",
events:[
new kintoneEvent({label:"一覧画面を表示した後", eventId:"app.record.index.show"}),
new kintoneEvent({label:"インライン編集を開始したとき",eventId:"app.record.index.edit.show"}),
new kintoneEvent({label:"インライン編集のフィールド値を変更したとき", eventId:"app.record.index.edit.change"}),
new kintoneEvent({label:"インライン編集の【保存】をクリックしたとき",eventId:"app.record.index.edit.submit"}),
new kintoneEvent({label:"インライン編集の保存が成功したとき", eventId:"app.record.index.edit.submit.success"}),
]
}
]);
// export const kintoneEvents:KintoneEventManager = new KintoneEventManager([
// 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.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.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.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.customButtonClick','ボタンをクリックした時',[],"app.record.index"),
// ],"")
// ]);

View File

@@ -76,7 +76,7 @@
"@nodelib/fs.stat" "2.0.5"
run-parallel "^1.1.9"
"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
version "2.0.5"
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
@@ -299,7 +299,7 @@
semver "^7.3.7"
tsutils "^3.21.0"
"@typescript-eslint/parser@^5.0.0", "@typescript-eslint/parser@^5.10.0":
"@typescript-eslint/parser@^5.10.0":
version "5.61.0"
resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.61.0.tgz"
integrity sha512-yGr4Sgyh8uO6fSi9hw3jAFXNBHbCtKKFMdX2IkT3ZqpKmtAq3lHS4ixB/COFuAIJpwl9/AqF7j72ZDWYKmIfvg==
@@ -367,7 +367,7 @@
"@typescript-eslint/types" "5.61.0"
eslint-visitor-keys "^3.3.0"
"@vitejs/plugin-vue@^2.0.0 || ^3.0.0 || ^4.0.0", "@vitejs/plugin-vue@^2.2.0":
"@vitejs/plugin-vue@^2.2.0":
version "2.3.4"
resolved "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-2.3.4.tgz"
integrity sha512-IfFNbtkbIm36O9KB8QodlwwYvTEsJb4Lll4c2IwB3VHc2gie2mSPtSzL0eYay7X2jd/2WX02FjSGTWR6OPr/zg==
@@ -480,22 +480,12 @@ acorn-jsx@^5.3.2:
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.9.0:
acorn@^8.9.0:
version "8.10.0"
resolved "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz"
integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
ajv@^6.10.0:
version "6.12.6"
resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
dependencies:
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ajv@^6.12.4:
ajv@^6.10.0, ajv@^6.12.4:
version "6.12.6"
resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
@@ -691,7 +681,7 @@ braces@^3.0.2, braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
browserslist@^4.21.5, "browserslist@>= 4.21.0":
browserslist@^4.21.5:
version "4.21.9"
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz"
integrity sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==
@@ -763,7 +753,7 @@ chardet@^0.7.0:
resolved "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz"
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
chokidar@^3.5.3, "chokidar@>=3.0.0 <4.0.0":
"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.3:
version "3.5.3"
resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz"
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
@@ -948,27 +938,6 @@ csstype@^3.1.1:
resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz"
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
debug@^4.1.1:
version "4.3.4"
resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.1.2"
debug@^4.3.2:
version "4.3.4"
resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.1.2"
debug@^4.3.4:
version "4.3.4"
resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.1.2"
debug@2.6.9:
version "2.6.9"
resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
@@ -976,6 +945,13 @@ debug@2.6.9:
dependencies:
ms "2.0.0"
debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
version "4.3.4"
resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.1.2"
deep-is@^0.1.3:
version "0.1.4"
resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz"
@@ -1068,12 +1044,107 @@ end-of-stream@^1.4.1:
dependencies:
once "^1.4.0"
esbuild-android-64@0.14.51:
version "0.14.51"
resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.51.tgz#414a087cb0de8db1e347ecca6c8320513de433db"
integrity sha512-6FOuKTHnC86dtrKDmdSj2CkcKF8PnqkaIXqvgydqfJmqBazCPdw+relrMlhGjkvVdiiGV70rpdnyFmA65ekBCQ==
esbuild-android-arm64@0.14.51:
version "0.14.51"
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.51.tgz#55de3bce2aab72bcd2b606da4318ad00fb9c8151"
integrity sha512-vBtp//5VVkZWmYYvHsqBRCMMi1MzKuMIn5XDScmnykMTu9+TD9v0NMEDqQxvtFToeYmojdo5UCV2vzMQWJcJ4A==
esbuild-darwin-64@0.14.51:
version "0.14.51"
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.51.tgz#4259f23ed6b4cea2ec8a28d87b7fb9801f093754"
integrity sha512-YFmXPIOvuagDcwCejMRtCDjgPfnDu+bNeh5FU2Ryi68ADDVlWEpbtpAbrtf/lvFTWPexbgyKgzppNgsmLPr8PA==
esbuild-darwin-arm64@0.14.51:
version "0.14.51"
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.51.tgz#d77b4366a71d84e530ba019d540b538b295d494a"
integrity sha512-juYD0QnSKwAMfzwKdIF6YbueXzS6N7y4GXPDeDkApz/1RzlT42mvX9jgNmyOlWKN7YzQAYbcUEJmZJYQGdf2ow==
esbuild-freebsd-64@0.14.51:
version "0.14.51"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.51.tgz#27b6587b3639f10519c65e07219d249b01f2ad38"
integrity sha512-cLEI/aXjb6vo5O2Y8rvVSQ7smgLldwYY5xMxqh/dQGfWO+R1NJOFsiax3IS4Ng300SVp7Gz3czxT6d6qf2cw0g==
esbuild-freebsd-arm64@0.14.51:
version "0.14.51"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.51.tgz#63c435917e566808c71fafddc600aca4d78be1ec"
integrity sha512-TcWVw/rCL2F+jUgRkgLa3qltd5gzKjIMGhkVybkjk6PJadYInPtgtUBp1/hG+mxyigaT7ib+od1Xb84b+L+1Mg==
esbuild-linux-32@0.14.51:
version "0.14.51"
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.51.tgz#c3da774143a37e7f11559b9369d98f11f997a5d9"
integrity sha512-RFqpyC5ChyWrjx8Xj2K0EC1aN0A37H6OJfmUXIASEqJoHcntuV3j2Efr9RNmUhMfNE6yEj2VpYuDteZLGDMr0w==
esbuild-linux-64@0.14.51:
version "0.14.51"
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.51.tgz#5d92b67f674e02ae0b4a9de9a757ba482115c4ae"
integrity sha512-dxjhrqo5i7Rq6DXwz5v+MEHVs9VNFItJmHBe1CxROWNf4miOGoQhqSG8StStbDkQ1Mtobg6ng+4fwByOhoQoeA==
esbuild-linux-arm64@0.14.51:
version "0.14.51"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.51.tgz#dac84740516e859d8b14e1ecc478dd5241b10c93"
integrity sha512-D9rFxGutoqQX3xJPxqd6o+kvYKeIbM0ifW2y0bgKk5HPgQQOo2k9/2Vpto3ybGYaFPCE5qTGtqQta9PoP6ZEzw==
esbuild-linux-arm@0.14.51:
version "0.14.51"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.51.tgz#b3ae7000696cd53ed95b2b458554ff543a60e106"
integrity sha512-LsJynDxYF6Neg7ZC7748yweCDD+N8ByCv22/7IAZglIEniEkqdF4HCaa49JNDLw1UQGlYuhOB8ZT/MmcSWzcWg==
esbuild-linux-mips64le@0.14.51:
version "0.14.51"
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.51.tgz#dad10770fac94efa092b5a0643821c955a9dd385"
integrity sha512-vS54wQjy4IinLSlb5EIlLoln8buh1yDgliP4CuEHumrPk4PvvP4kTRIG4SzMXm6t19N0rIfT4bNdAxzJLg2k6A==
esbuild-linux-ppc64le@0.14.51:
version "0.14.51"
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.51.tgz#b68c2f8294d012a16a88073d67e976edd4850ae0"
integrity sha512-xcdd62Y3VfGoyphNP/aIV9LP+RzFw5M5Z7ja+zdpQHHvokJM7d0rlDRMN+iSSwvUymQkqZO+G/xjb4/75du8BQ==
esbuild-linux-riscv64@0.14.51:
version "0.14.51"
resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.51.tgz#608a318b8697123e44c1e185cdf6708e3df50b93"
integrity sha512-syXHGak9wkAnFz0gMmRBoy44JV0rp4kVCEA36P5MCeZcxFq8+fllBC2t6sKI23w3qd8Vwo9pTADCgjTSf3L3rA==
esbuild-linux-s390x@0.14.51:
version "0.14.51"
resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.51.tgz#c9e7791170a3295dba79b93aa452beb9838a8625"
integrity sha512-kFAJY3dv+Wq8o28K/C7xkZk/X34rgTwhknSsElIqoEo8armCOjMJ6NsMxm48KaWY2h2RUYGtQmr+RGuUPKBhyw==
esbuild-netbsd-64@0.14.51:
version "0.14.51"
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.51.tgz#0abd40b8c2e37fda6f5cc41a04cb2b690823d891"
integrity sha512-ZZBI7qrR1FevdPBVHz/1GSk1x5GDL/iy42Zy8+neEm/HA7ma+hH/bwPEjeHXKWUDvM36CZpSL/fn1/y9/Hb+1A==
esbuild-openbsd-64@0.14.51:
version "0.14.51"
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.51.tgz#4adba0b7ea7eb1428bb00d8e94c199a949b130e8"
integrity sha512-7R1/p39M+LSVQVgDVlcY1KKm6kFKjERSX1lipMG51NPcspJD1tmiZSmmBXoY5jhHIu6JL1QkFDTx94gMYK6vfA==
esbuild-sunos-64@0.14.51:
version "0.14.51"
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.51.tgz#4b8a6d97dfedda30a6e39607393c5c90ebf63891"
integrity sha512-HoHaCswHxLEYN8eBTtyO0bFEWvA3Kdb++hSQ/lLG7TyKF69TeSG0RNoBRAs45x/oCeWaTDntEZlYwAfQlhEtJA==
esbuild-windows-32@0.14.51:
version "0.14.51"
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.51.tgz#d31d8ca0c1d314fb1edea163685a423b62e9ac17"
integrity sha512-4rtwSAM35A07CBt1/X8RWieDj3ZUHQqUOaEo5ZBs69rt5WAFjP4aqCIobdqOy4FdhYw1yF8Z0xFBTyc9lgPtEg==
esbuild-windows-64@0.14.51:
version "0.14.51"
resolved "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.51.tgz"
integrity sha512-HoN/5HGRXJpWODprGCgKbdMvrC3A2gqvzewu2eECRw2sYxOUoh2TV1tS+G7bHNapPGI79woQJGV6pFH7GH7qnA==
esbuild@^0.14.27, esbuild@0.14.51:
esbuild-windows-arm64@0.14.51:
version "0.14.51"
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.51.tgz#0220d2304bfdc11bc27e19b2aaf56edf183e4ae9"
integrity sha512-JQDqPjuOH7o+BsKMSddMfmVJXrnYZxXDHsoLHc0xgmAZkOOCflRmC43q31pk79F9xuyWY45jDBPolb5ZgGOf9g==
esbuild@0.14.51, esbuild@^0.14.27:
version "0.14.51"
resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.14.51.tgz"
integrity sha512-+CvnDitD7Q5sT7F+FM65sWkF8wJRf+j9fPcprxYV4j+ohmzVj2W7caUqH2s5kCaCJAfcAICjSlKhDCcvDpU7nw==
@@ -1145,15 +1216,7 @@ eslint-scope@^5.1.1:
esrecurse "^4.3.0"
estraverse "^4.1.1"
eslint-scope@^7.1.1:
version "7.2.0"
resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz"
integrity sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==
dependencies:
esrecurse "^4.3.0"
estraverse "^5.2.0"
eslint-scope@^7.2.0:
eslint-scope@^7.1.1, eslint-scope@^7.2.0:
version "7.2.0"
resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz"
integrity sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==
@@ -1166,7 +1229,7 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1:
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz"
integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==
eslint@*, "eslint@^6.0.0 || ^7.0.0 || ^8.0.0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^6.2.0 || ^7.0.0 || ^8.0.0", eslint@^8.10.0, eslint@^8.11.0, eslint@>=6.0.0, eslint@>=7.0.0:
eslint@^8.10.0:
version "8.44.0"
resolved "https://registry.npmjs.org/eslint/-/eslint-8.44.0.tgz"
integrity sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==
@@ -1239,12 +1302,7 @@ estraverse@^4.1.1:
resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz"
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
estraverse@^5.1.0:
version "5.3.0"
resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz"
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
estraverse@^5.2.0:
estraverse@^5.1.0, estraverse@^5.2.0:
version "5.3.0"
resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz"
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
@@ -1315,7 +1373,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-glob@^3.2.9, fast-glob@3.2.12:
fast-glob@3.2.12, fast-glob@^3.2.9:
version "3.2.12"
resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz"
integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==
@@ -1446,6 +1504,11 @@ fs.realpath@^1.0.0:
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
fsevents@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz"
@@ -1572,7 +1635,7 @@ http-errors@2.0.0:
statuses "2.0.1"
toidentifier "1.0.1"
iconv-lite@^0.4.24, iconv-lite@0.4.24:
iconv-lite@0.4.24, iconv-lite@^0.4.24:
version "0.4.24"
resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz"
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
@@ -1610,7 +1673,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@2, inherits@2.0.4:
inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -1894,7 +1957,7 @@ micromatch@^4.0.4:
braces "^3.0.2"
picomatch "^2.3.1"
"mime-db@>= 1.43.0 < 2", mime-db@1.52.0:
mime-db@1.52.0, "mime-db@>= 1.43.0 < 2":
version "1.52.0"
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
@@ -2149,7 +2212,7 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1:
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
pinia@^2.0.0, pinia@^2.1.6:
pinia@^2.1.6:
version "2.1.6"
resolved "https://registry.npmjs.org/pinia/-/pinia-2.1.6.tgz"
integrity sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==
@@ -2170,7 +2233,7 @@ postcss-value-parser@^4.2.0:
resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^8.1.0, postcss@^8.1.10, postcss@^8.4.13:
postcss@^8.1.10, postcss@^8.4.13:
version "8.4.25"
resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.25.tgz"
integrity sha512-7taJ/8t2av0Z+sQEvNzCkpDynl0tX3uJMCODi6nT3PfASC7dYCWV9aQ+uiCf+KBD4SEFcu+GvJdGdwzQ6OSjCw==
@@ -2219,7 +2282,7 @@ qs@6.11.0:
dependencies:
side-channel "^1.0.4"
quasar@^2.6.0, quasar@^2.8.0:
quasar@^2.6.0:
version "2.12.2"
resolved "https://registry.npmjs.org/quasar/-/quasar-2.12.2.tgz"
integrity sha512-UB+J1cN6b9FrRphUuMvW1Pt1uaOPH2XJMAZagQlYzVUmltKCXQ0sby+ANgGRYa8w/tsekMySmN9QvkQ3+hYv/g==
@@ -2251,20 +2314,7 @@ raw-body@2.5.1:
iconv-lite "0.4.24"
unpipe "1.0.0"
readable-stream@^2.0.0:
version "2.3.8"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz"
integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.3"
isarray "~1.0.0"
process-nextick-args "~2.0.0"
safe-buffer "~5.1.1"
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
readable-stream@^2.0.5:
readable-stream@^2.0.0, readable-stream@^2.0.5:
version "2.3.8"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz"
integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
@@ -2364,7 +2414,7 @@ rollup-plugin-visualizer@^5.5.4:
source-map "^0.7.4"
yargs "^17.5.1"
"rollup@>=2.59.0 <2.78.0", "rollup@2.x || 3.x":
"rollup@>=2.59.0 <2.78.0":
version "2.77.3"
resolved "https://registry.npmjs.org/rollup/-/rollup-2.77.3.tgz"
integrity sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==
@@ -2390,7 +2440,7 @@ rxjs@^7.5.5:
dependencies:
tslib "^2.1.0"
safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1, safe-buffer@5.1.2:
safe-buffer@5.1.2, safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.2"
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
@@ -2405,7 +2455,7 @@ safe-buffer@5.2.1:
resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sass@*, sass@1.32.12:
sass@1.32.12:
version "1.32.12"
resolved "https://registry.npmjs.org/sass/-/sass-1.32.12.tgz"
integrity sha512-zmXn03k3hN0KaiVTjohgkg98C3UowhL1/VSGdj4/VAAiMKGQOE80PFPxFP2Kyq0OUskPKcY5lImkhBKEHlypJA==
@@ -2537,13 +2587,6 @@ statuses@2.0.1:
resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz"
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
string_decoder@^1.1.1, string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz"
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
dependencies:
safe-buffer "~5.1.0"
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
@@ -2553,6 +2596,13 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string_decoder@^1.1.1, string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz"
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
dependencies:
safe-buffer "~5.1.0"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
@@ -2670,7 +2720,7 @@ type-is@~1.6.18:
media-typer "0.3.0"
mime-types "~2.1.24"
typescript@^4.5.4, "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta", typescript@>=4.4.4:
typescript@^4.5.4:
version "4.9.5"
resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz"
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
@@ -2685,7 +2735,7 @@ universalify@^2.0.0:
resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz"
integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
unpipe@~1.0.0, unpipe@1.0.0:
unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz"
integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
@@ -2730,7 +2780,7 @@ vary@~1.1.2:
resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz"
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
"vite@^2.0.0 || ^3.0.0 || ^4.0.0", vite@^2.5.10, vite@^2.9.13:
vite@^2.9.13:
version "2.9.16"
resolved "https://registry.npmjs.org/vite/-/vite-2.9.16.tgz"
integrity sha512-X+6q8KPyeuBvTQV8AVSnKDvXoBMnTx8zxh54sOwmmuOdxkjMmEJXH2UEchA+vTMps1xw9vL64uwJOWryULg7nA==
@@ -2760,14 +2810,14 @@ vue-eslint-parser@^9.3.0:
lodash "^4.17.21"
semver "^7.3.6"
vue-router@^4.0.0, vue-router@^4.0.12:
vue-router@^4.0.0:
version "4.2.4"
resolved "https://registry.npmjs.org/vue-router/-/vue-router-4.2.4.tgz"
integrity sha512-9PISkmaCO02OzPVOMq2w82ilty6+xJmQrarYZDkjZBfl4RvYAlt4PKnEX21oW4KTtWfa9OuO/b3qk1Od3AEdCQ==
dependencies:
"@vue/devtools-api" "^6.5.0"
"vue@^2.6.14 || ^3.3.0", vue@^3.0.0, "vue@^3.0.0-0 || ^2.6.0", vue@^3.2.0, vue@^3.2.25, vue@^3.2.29, vue@3.3.4:
vue@^3.0.0:
version "3.3.4"
resolved "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz"
integrity sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==

6
node_modules/.package-lock.json generated vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"name": "App Builder for kintone",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

15
node_modules/.yarn-integrity generated vendored Normal file
View File

@@ -0,0 +1,15 @@
{
"systemParams": "win32-x64-108",
"modulesFolders": [
"node_modules"
],
"flags": [],
"linkedModules": [
"@quasar\\quasar-ui-qactivity",
"docs"
],
"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

24
plugin/kintone-addins/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

814
plugin/kintone-addins/package-lock.json generated Normal file
View File

@@ -0,0 +1,814 @@
{
"name": "kintone-addins",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "kintone-addins",
"version": "0.0.0",
"dependencies": {
"jquery": "^3.7.1",
"yarn": "^1.22.22"
},
"devDependencies": {
"@types/jquery": "^3.5.24",
"@types/node": "^20.8.9",
"sass": "^1.69.5",
"typescript": "^5.0.2",
"vite": "^4.4.5"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
"integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
"integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
"integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
"integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
"integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
"integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
"integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
"integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
"integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
"integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
"integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
"integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
"cpu": [
"mips64el"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
"integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
"integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
"integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
"integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
"integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
"integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
"integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
"integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
"integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
"integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@types/jquery": {
"version": "3.5.24",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.24.tgz",
"integrity": "sha512-V/TG69ge5amcr8Ap7vY3SObqKfZlV7ttqcYnNcYnndI77ySIRi05+3GjvfwRtE2qalAC2ySLIL1ker512sI20g==",
"dev": true,
"dependencies": {
"@types/sizzle": "*"
}
},
"node_modules/@types/node": {
"version": "20.11.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.0.tgz",
"integrity": "sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@types/sizzle": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.5.tgz",
"integrity": "sha512-tAe4Q+OLFOA/AMD+0lq8ovp8t3ysxAOeaScnfNdZpUxaGl51ZMDEITxkvFl1STudQ58mz6gzVGl9VhMKhwRnZQ==",
"dev": true
},
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"dependencies": {
"fill-range": "^7.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/esbuild": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
"integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
"dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/android-arm": "0.18.20",
"@esbuild/android-arm64": "0.18.20",
"@esbuild/android-x64": "0.18.20",
"@esbuild/darwin-arm64": "0.18.20",
"@esbuild/darwin-x64": "0.18.20",
"@esbuild/freebsd-arm64": "0.18.20",
"@esbuild/freebsd-x64": "0.18.20",
"@esbuild/linux-arm": "0.18.20",
"@esbuild/linux-arm64": "0.18.20",
"@esbuild/linux-ia32": "0.18.20",
"@esbuild/linux-loong64": "0.18.20",
"@esbuild/linux-mips64el": "0.18.20",
"@esbuild/linux-ppc64": "0.18.20",
"@esbuild/linux-riscv64": "0.18.20",
"@esbuild/linux-s390x": "0.18.20",
"@esbuild/linux-x64": "0.18.20",
"@esbuild/netbsd-x64": "0.18.20",
"@esbuild/openbsd-x64": "0.18.20",
"@esbuild/sunos-x64": "0.18.20",
"@esbuild/win32-arm64": "0.18.20",
"@esbuild/win32-ia32": "0.18.20",
"@esbuild/win32-x64": "0.18.20"
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/immutable": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz",
"integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==",
"dev": true
},
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"dependencies": {
"binary-extensions": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/jquery": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
},
"node_modules/nanoid": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/rollup": {
"version": "3.29.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
"integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==",
"dev": true,
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=14.18.0",
"npm": ">=8.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/sass": {
"version": "1.69.7",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.69.7.tgz",
"integrity": "sha512-rzj2soDeZ8wtE2egyLXgOOHQvaC2iosZrkF6v3EUG+tBwEvhqUCzm0VP3k9gHF9LXbSrRhT5SksoI56Iw8NPnQ==",
"dev": true,
"dependencies": {
"chokidar": ">=3.0.0 <4.0.0",
"immutable": "^4.0.0",
"source-map-js": ">=0.6.2 <2.0.0"
},
"bin": {
"sass": "sass.js"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/typescript": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
},
"node_modules/vite": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz",
"integrity": "sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==",
"dev": true,
"dependencies": {
"esbuild": "^0.18.10",
"postcss": "^8.4.27",
"rollup": "^3.27.1"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
},
"peerDependencies": {
"@types/node": ">= 14",
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"less": {
"optional": true
},
"lightningcss": {
"optional": true
},
"sass": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"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

@@ -0,0 +1,38 @@
{
"name": "kintone-addins",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"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 copyjs:windows copycss:windows",
"build:dev": "run-s b:dev copyjs:windows copycss:windows",
"build:linux": "run-s b:production copyjs:linux copycss:linux",
"build:linux-dev": "run-s b:dev copy:linux",
"b:production": "tsc & vite build --mode production",
"b:dev": "tsc & vite build --mode dev",
"copyjs:windows": "xcopy dist\\*.js ..\\..\\backend\\Temp\\ /E /I /Y",
"copyjs:linux": "cp -ur dist/*.js ../../backend/Temp",
"copycss:windows": "xcopy dist\\*.css ..\\..\\backend\\Temp\\ /E /I /Y",
"copycss:linux": "cp -ur dist/*.css ../../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-plugin-checker": "^0.6.4"
},
"dependencies": {
"@kintone/rest-api-client": "^5.5.2",
"@popperjs/core": "^2.11.8",
"@types/bootstrap": "^5.2.10",
"bootstrap": "^5.3.3",
"jquery": "^3.7.1"
}
}

Some files were not shown because too many files have changed in this diff Show More