From d25f8f49dae8eba1d28ef0a400be3dfb53525c71 Mon Sep 17 00:00:00 2001 From: Geriano Date: Tue, 26 Jul 2022 09:05:28 +0700 Subject: [PATCH] now menu can be drag and drop --- .../Controllers/Superuser/MenuController.php | 176 +++++------------- package-lock.json | 102 +++++----- package.json | 3 +- resources/js/Pages/Superuser/Menu/Index.vue | 24 ++- resources/js/Pages/Superuser/Menu/Nested.vue | 38 ++++ routes/web.php | 9 +- 6 files changed, 147 insertions(+), 205 deletions(-) create mode 100644 resources/js/Pages/Superuser/Menu/Nested.vue diff --git a/app/Http/Controllers/Superuser/MenuController.php b/app/Http/Controllers/Superuser/MenuController.php index 30ee891..55cc4c0 100644 --- a/app/Http/Controllers/Superuser/MenuController.php +++ b/app/Http/Controllers/Superuser/MenuController.php @@ -6,6 +6,7 @@ use App\Http\Controllers\Controller; use App\Models\Menu; use App\Models\Permission; use Illuminate\Http\Request; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Route; use Inertia\Inertia; @@ -169,171 +170,80 @@ class MenuController extends Controller } /** - * @param \App\Models\Menu $menu + * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ - public function up(Menu $menu) + public function save(Request $request) { - $before = Menu::where('parent_id', $menu->parent_id) - ->where('position', $menu->position - 1) - ->first(); + $request->validate([ + 'menus' => 'required', + ]); - if (!$before) { - return redirect()->back()->with('error', __( - 'can\'t find menu before `:name`', [ - 'name' => $menu->name, - ] - )); - } + $menus = collect($this->positions($request->menus))->flatMap([$this, 'flatMap']); DB::beginTransaction(); try { - Menu::where('id', $before->id)->update([ - 'position' => $menu->position, - ]); - - $menu->update([ - 'position' => $before->position, - ]); + $menus->each(function (Menu $menu) { + Menu::where('id', $menu->id)->update($menu->only([ + 'parent_id', + 'position', + ])); + }); DB::commit(); + + return redirect()->back()->with('success', __( + 'menu positions has been saved', + )); } catch (Throwable $e) { DB::rollBack(); - return redirect()->back()->with('error', __($e->getMessage())); + return redirect()->back()->with('error', __( + $e->getMessage(), + )); } - - return redirect()->back()->with('success', __( - 'position has been updated', - )); } /** * @param \App\Models\Menu $menu - * @return \Illuminate\Http\Response + * @return array */ - public function down(Menu $menu) + public function flatMap(Menu $menu) { - $after = Menu::where('parent_id', $menu->parent_id) - ->where('position', $menu->position + 1) - ->first(); - - if (!$after) { - return redirect()->back()->with('error', __( - 'can\'t find menu after `:name`', [ - 'name' => $menu->name, - ] - )); - } - - DB::beginTransaction(); - - try { - Menu::where('id', $after->id)->update([ - 'position' => $menu->position, - ]); - - $menu->update([ - 'position' => $after->position, - ]); - - DB::commit(); - } catch (Throwable $e) { - DB::rollBack(); - - return redirect()->back()->with('error', __($e->getMessage())); - } - - return redirect()->back()->with('success', __( - 'position has been updated', - )); + return [ + $menu, + ...collect($menu->childs)->flatMap([$this, 'flatMap']), + ]; } /** - * @param \App\Models\Menu $menu - * @return \Illuminate\Http\Response + * @param array $menu + * @return array */ - public function right(Menu $menu) + public function updateWithChild(array $menu) { - $before = Menu::where('parent_id', $menu->parent_id) - ->where('position', $menu->position - 1) - ->withCount('childs') - ->first(); - - if (!$before) { - return redirect()->back()->with('error', __( - 'can\'t find menu before `:name`', [ - 'name' => $menu->name, - ] - )); + if (array_key_exists('childs', $menu) && $menu['childs']) { + return $this->updateWithChilds($menu['childs']); } - DB::beginTransaction(); - - try { - Menu::where('parent_id', $menu->parent_id) - ->where('position', '>', $menu->position) - ->decrement('position'); - - $menu->update([ - 'parent_id' => $before->id, - 'position' => $before->childs_count + 1, - ]); - - DB::commit(); - } catch (Throwable $e) { - DB::rollBack(); - - return redirect()->back()->with('error', __($e->getMessage())); - } - - return redirect()->back()->with('success', __( - 'position has been updated', - )); + return new Menu($menu); } /** - * @param \App\Models\Menu $menu - * @return \Illuminate\Http\Response + * @param \Illuminate\Support\Collection|\App\Models\Menu|array + * @param int $parent */ - public function left(Menu $menu) + private function positions(Collection|Menu|array $menus, int $parent = null) { - $parent = $menu->parent; + return array_map(function ($menu) use (&$i, $parent) { + $new = new Menu($menu); + $new->id = $menu['id']; + $new->position = ++$i; + $new->parent_id = $parent; + $new->childs = array_key_exists('childs', $menu) ? $this->positions($menu['childs'], $new->id) : []; - if (!$parent) { - return redirect()->back()->with('error', __( - 'can\'t find parent menu `:name`', [ - 'name' => $menu->name, - ] - )); - } - - DB::beginTransaction(); - - try { - Menu::where('parent_id', $parent->parent_id) - ->where('position', '>', $parent->position) - ->increment('position'); - - Menu::where('parent_id', $menu->parent_id) - ->where('position', '>', $menu->position) - ->decrement('position'); - - $menu->update([ - 'parent_id' => $parent->parent_id, - 'position' => $parent->position + 1, - ]); - - DB::commit(); - } catch (Throwable $e) { - DB::rollBack(); - - return redirect()->back()->with('error', __($e->getMessage())); - } - - return redirect()->back()->with('success', __( - 'position has been updated', - )); + return $new; + }, $menus); } } diff --git a/package-lock.json b/package-lock.json index 01590e1..4aa682b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,8 @@ "": { "dependencies": { "@vueform/multiselect": "^2.5.1", - "sweetalert2": "^11.4.23" + "sweetalert2": "^11.4.23", + "vuedraggable": "^4.1.0" }, "devDependencies": { "@inertiajs/inertia": "^0.11.0", @@ -29,7 +30,6 @@ "version": "7.18.8", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.8.tgz", "integrity": "sha512-RSKRfYX20dyH+elbJK2uqAkVyucL+xXzhqlMD5/ZXx+dAAwpyB7HsvnHe/ZUGOF+xLr5Wx9/JoXVTj6BQE2/oA==", - "dev": true, "bin": { "parser": "bin/babel-parser.js" }, @@ -161,7 +161,6 @@ "version": "3.2.37", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.37.tgz", "integrity": "sha512-81KhEjo7YAOh0vQJoSmAD68wLfYqJvoiD4ulyedzF+OEk/bk6/hx3fTNVfuzugIIaTrOx4PGx6pAiBRe5e9Zmg==", - "dev": true, "dependencies": { "@babel/parser": "^7.16.4", "@vue/shared": "3.2.37", @@ -173,7 +172,6 @@ "version": "3.2.37", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.37.tgz", "integrity": "sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ==", - "dev": true, "dependencies": { "@vue/compiler-core": "3.2.37", "@vue/shared": "3.2.37" @@ -183,7 +181,6 @@ "version": "3.2.37", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.37.tgz", "integrity": "sha512-+7i/2+9LYlpqDv+KTtWhOZH+pa8/HnX/905MdVmAcI/mPQOBwkHHIzrsEsucyOIZQYMkXUiTkmZq5am/NyXKkg==", - "dev": true, "dependencies": { "@babel/parser": "^7.16.4", "@vue/compiler-core": "3.2.37", @@ -201,7 +198,6 @@ "version": "3.2.37", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.37.tgz", "integrity": "sha512-7mQJD7HdXxQjktmsWp/J67lThEIcxLemz1Vb5I6rYJHR5vI+lON3nPGOH3ubmbvYGt8xEUaAr1j7/tIFWiEOqw==", - "dev": true, "dependencies": { "@vue/compiler-dom": "3.2.37", "@vue/shared": "3.2.37" @@ -211,7 +207,6 @@ "version": "3.2.37", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.37.tgz", "integrity": "sha512-/7WRafBOshOc6m3F7plwzPeCu/RCVv9uMpOwa/5PiY1Zz+WLVRWiy0MYKwmg19KBdGtFWsmZ4cD+LOdVPcs52A==", - "dev": true, "dependencies": { "@vue/shared": "3.2.37" } @@ -220,7 +215,6 @@ "version": "3.2.37", "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.37.tgz", "integrity": "sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg==", - "dev": true, "dependencies": { "@babel/parser": "^7.16.4", "@vue/compiler-core": "3.2.37", @@ -233,7 +227,6 @@ "version": "3.2.37", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.37.tgz", "integrity": "sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ==", - "dev": true, "dependencies": { "@vue/reactivity": "3.2.37", "@vue/shared": "3.2.37" @@ -243,7 +236,6 @@ "version": "3.2.37", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.37.tgz", "integrity": "sha512-HimKdh9BepShW6YozwRKAYjYQWg9mQn63RGEiSswMbW+ssIht1MILYlVGkAGGQbkhSh31PCdoUcfiu4apXJoPw==", - "dev": true, "dependencies": { "@vue/runtime-core": "3.2.37", "@vue/shared": "3.2.37", @@ -254,7 +246,6 @@ "version": "3.2.37", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.37.tgz", "integrity": "sha512-kLITEJvaYgZQ2h47hIzPh2K3jG8c1zCVbp/o/bzQOyvzaKiCquKS7AaioPI28GNxIsE/zSx+EwWYsNxDCX95MA==", - "dev": true, "dependencies": { "@vue/compiler-ssr": "3.2.37", "@vue/shared": "3.2.37" @@ -266,8 +257,7 @@ "node_modules/@vue/shared": { "version": "3.2.37", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.37.tgz", - "integrity": "sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw==", - "dev": true + "integrity": "sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw==" }, "node_modules/@vueform/multiselect": { "version": "2.5.1", @@ -514,8 +504,7 @@ "node_modules/csstype": { "version": "2.6.20", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz", - "integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==", - "dev": true + "integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==" }, "node_modules/deepmerge": { "version": "4.2.2", @@ -934,8 +923,7 @@ "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" }, "node_modules/fast-glob": { "version": "3.2.11", @@ -1204,7 +1192,6 @@ "version": "0.25.9", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, "dependencies": { "sourcemap-codec": "^1.4.8" } @@ -1250,7 +1237,6 @@ "version": "3.3.4", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "dev": true, "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -1315,8 +1301,7 @@ "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 + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -1343,7 +1328,6 @@ "version": "8.4.14", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", - "dev": true, "funding": [ { "type": "opencollective", @@ -1613,11 +1597,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sortablejs": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz", + "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==" + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -1626,7 +1614,6 @@ "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" } @@ -1634,8 +1621,7 @@ "node_modules/sourcemap-codec": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", @@ -1783,7 +1769,6 @@ "version": "3.2.37", "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.37.tgz", "integrity": "sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ==", - "dev": true, "dependencies": { "@vue/compiler-dom": "3.2.37", "@vue/compiler-sfc": "3.2.37", @@ -1792,6 +1777,17 @@ "@vue/shared": "3.2.37" } }, + "node_modules/vuedraggable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz", + "integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==", + "dependencies": { + "sortablejs": "1.14.0" + }, + "peerDependencies": { + "vue": "^3.0.1" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -1815,8 +1811,7 @@ "@babel/parser": { "version": "7.18.8", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.8.tgz", - "integrity": "sha512-RSKRfYX20dyH+elbJK2uqAkVyucL+xXzhqlMD5/ZXx+dAAwpyB7HsvnHe/ZUGOF+xLr5Wx9/JoXVTj6BQE2/oA==", - "dev": true + "integrity": "sha512-RSKRfYX20dyH+elbJK2uqAkVyucL+xXzhqlMD5/ZXx+dAAwpyB7HsvnHe/ZUGOF+xLr5Wx9/JoXVTj6BQE2/oA==" }, "@inertiajs/inertia": { "version": "0.11.0", @@ -1916,7 +1911,6 @@ "version": "3.2.37", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.37.tgz", "integrity": "sha512-81KhEjo7YAOh0vQJoSmAD68wLfYqJvoiD4ulyedzF+OEk/bk6/hx3fTNVfuzugIIaTrOx4PGx6pAiBRe5e9Zmg==", - "dev": true, "requires": { "@babel/parser": "^7.16.4", "@vue/shared": "3.2.37", @@ -1928,7 +1922,6 @@ "version": "3.2.37", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.37.tgz", "integrity": "sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ==", - "dev": true, "requires": { "@vue/compiler-core": "3.2.37", "@vue/shared": "3.2.37" @@ -1938,7 +1931,6 @@ "version": "3.2.37", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.37.tgz", "integrity": "sha512-+7i/2+9LYlpqDv+KTtWhOZH+pa8/HnX/905MdVmAcI/mPQOBwkHHIzrsEsucyOIZQYMkXUiTkmZq5am/NyXKkg==", - "dev": true, "requires": { "@babel/parser": "^7.16.4", "@vue/compiler-core": "3.2.37", @@ -1956,7 +1948,6 @@ "version": "3.2.37", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.37.tgz", "integrity": "sha512-7mQJD7HdXxQjktmsWp/J67lThEIcxLemz1Vb5I6rYJHR5vI+lON3nPGOH3ubmbvYGt8xEUaAr1j7/tIFWiEOqw==", - "dev": true, "requires": { "@vue/compiler-dom": "3.2.37", "@vue/shared": "3.2.37" @@ -1966,7 +1957,6 @@ "version": "3.2.37", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.37.tgz", "integrity": "sha512-/7WRafBOshOc6m3F7plwzPeCu/RCVv9uMpOwa/5PiY1Zz+WLVRWiy0MYKwmg19KBdGtFWsmZ4cD+LOdVPcs52A==", - "dev": true, "requires": { "@vue/shared": "3.2.37" } @@ -1975,7 +1965,6 @@ "version": "3.2.37", "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.37.tgz", "integrity": "sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg==", - "dev": true, "requires": { "@babel/parser": "^7.16.4", "@vue/compiler-core": "3.2.37", @@ -1988,7 +1977,6 @@ "version": "3.2.37", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.37.tgz", "integrity": "sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ==", - "dev": true, "requires": { "@vue/reactivity": "3.2.37", "@vue/shared": "3.2.37" @@ -1998,7 +1986,6 @@ "version": "3.2.37", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.37.tgz", "integrity": "sha512-HimKdh9BepShW6YozwRKAYjYQWg9mQn63RGEiSswMbW+ssIht1MILYlVGkAGGQbkhSh31PCdoUcfiu4apXJoPw==", - "dev": true, "requires": { "@vue/runtime-core": "3.2.37", "@vue/shared": "3.2.37", @@ -2009,7 +1996,6 @@ "version": "3.2.37", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.37.tgz", "integrity": "sha512-kLITEJvaYgZQ2h47hIzPh2K3jG8c1zCVbp/o/bzQOyvzaKiCquKS7AaioPI28GNxIsE/zSx+EwWYsNxDCX95MA==", - "dev": true, "requires": { "@vue/compiler-ssr": "3.2.37", "@vue/shared": "3.2.37" @@ -2018,8 +2004,7 @@ "@vue/shared": { "version": "3.2.37", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.37.tgz", - "integrity": "sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw==", - "dev": true + "integrity": "sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw==" }, "@vueform/multiselect": { "version": "2.5.1", @@ -2179,8 +2164,7 @@ "csstype": { "version": "2.6.20", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz", - "integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==", - "dev": true + "integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==" }, "deepmerge": { "version": "4.2.2", @@ -2400,8 +2384,7 @@ "estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" }, "fast-glob": { "version": "3.2.11", @@ -2597,7 +2580,6 @@ "version": "0.25.9", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, "requires": { "sourcemap-codec": "^1.4.8" } @@ -2633,8 +2615,7 @@ "nanoid": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "dev": true + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" }, "node-releases": { "version": "2.0.6", @@ -2681,8 +2662,7 @@ "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "picomatch": { "version": "2.3.1", @@ -2700,7 +2680,6 @@ "version": "8.4.14", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", - "dev": true, "requires": { "nanoid": "^3.3.4", "picocolors": "^1.0.0", @@ -2847,23 +2826,25 @@ "object-inspect": "^1.9.0" } }, + "sortablejs": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz", + "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==" + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "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 + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" }, "sourcemap-codec": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" }, "supports-preserve-symlinks-flag": { "version": "1.0.0", @@ -2948,7 +2929,6 @@ "version": "3.2.37", "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.37.tgz", "integrity": "sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ==", - "dev": true, "requires": { "@vue/compiler-dom": "3.2.37", "@vue/compiler-sfc": "3.2.37", @@ -2957,6 +2937,14 @@ "@vue/shared": "3.2.37" } }, + "vuedraggable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz", + "integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==", + "requires": { + "sortablejs": "1.14.0" + } + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 209d71a..70859a6 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ }, "dependencies": { "@vueform/multiselect": "^2.5.1", - "sweetalert2": "^11.4.23" + "sweetalert2": "^11.4.23", + "vuedraggable": "^4.1.0" } } diff --git a/resources/js/Pages/Superuser/Menu/Index.vue b/resources/js/Pages/Superuser/Menu/Index.vue index 3f3ff03..3caea1f 100644 --- a/resources/js/Pages/Superuser/Menu/Index.vue +++ b/resources/js/Pages/Superuser/Menu/Index.vue @@ -8,7 +8,7 @@ import Icon from '@/Components/Icon.vue' import axios from 'axios' import Swal from 'sweetalert2' import Select from '@vueform/multiselect' -import Builder from './Builder.vue' +import Nested from './Nested.vue' const self = getCurrentInstance() const { permissions, routes, icons } = defineProps({ @@ -107,10 +107,9 @@ const destroy = async menu => { const submit = () => form.id ? update() : store() -const up = menu => Inertia.patch(route('superuser.menu.up', menu.id)) -const down = menu => Inertia.patch(route('superuser.menu.down', menu.id)) -const right = menu => Inertia.patch(route('superuser.menu.right', menu.id)) -const left = menu => Inertia.patch(route('superuser.menu.left', menu.id)) +const save = () => { + return useForm({ menus: menus.value }).patch(route('superuser.menu.save')) +} const esc = e => e.key === 'Escape' && close() @@ -143,8 +142,19 @@ onUnmounted(() => window.removeEventListener('keydown', esc)) + + diff --git a/resources/js/Pages/Superuser/Menu/Nested.vue b/resources/js/Pages/Superuser/Menu/Nested.vue new file mode 100644 index 0000000..d43b70e --- /dev/null +++ b/resources/js/Pages/Superuser/Menu/Nested.vue @@ -0,0 +1,38 @@ + + + \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index e04dd52..72e146c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -40,15 +40,10 @@ Route::middleware(['auth:sanctum', config('jetstream.auth_session'), 'verified'] Route::patch('/permission/{permission}/detach', 'detachPermission')->name('permission.detach'); }); + Route::patch('/menu/save', [App\Http\Controllers\Superuser\MenuController::class, 'save'])->name('menu.save'); Route::resource('menu', App\Http\Controllers\Superuser\MenuController::class)->only([ 'index', 'store', 'update', 'destroy', ]); - - Route::prefix('/menu/{menu}')->name('menu.')->controller(App\Http\Controllers\Superuser\MenuController::class)->group(function () { - Route::patch('/up', 'up')->name('up'); - Route::patch('/down', 'down')->name('down'); - Route::patch('/left', 'left')->name('left'); - Route::patch('/right', 'right')->name('right'); - }); + }); }); \ No newline at end of file