mirror of
https://github.com/filamentphp/filament.git
synced 2026-09-24 15:42:09 +08:00
Merge branch '3.x' into 3.x
This commit is contained in:
@@ -1,4 +1 @@
|
||||
@danharrin
|
||||
|
||||
*.blade.php @zepfietje
|
||||
*.css @zepfietje
|
||||
|
||||
@@ -89,7 +89,7 @@ body:
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Reproduction repository
|
||||
description: The URL of a public GitHub repository which reproduces the problem. **Please do not link to your actual project**, what we need instead is a _minimal_ reproduction in a fresh project without any unnecessary code. This means it doesn\'t matter if your real project is private / confidential, since we want a link to a separate, isolated reproduction. This allows us to fix the problem much quicker. **This issue will be automatically closed and not reviewed if this is missing. Please make sure to format the URL starting with `https://github.com` - only repositories hosted on GitHub are accepted.** [Need a headstart? We have a template Filament project for you.](https://filament-issue.unitedbycode.com/)
|
||||
description: The URL of a public GitHub repository which reproduces the problem. **Please do not link to your actual project**, what we need instead is a _minimal_ reproduction in a fresh project without any unnecessary code. This means it doesn\'t matter if your real project is private / confidential, since we want a link to a separate, isolated reproduction. This allows us to fix the problem much quicker. **This issue will be automatically closed and not reviewed if this is missing. Please make sure to format the URL starting with `https://github.com` - only repositories hosted on GitHub are accepted.** [Need a headstart? We have a template Filament project for you.](https://filament-issue.unitedbycode.com)
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
+4
-7
@@ -55,8 +55,8 @@ further defined and clarified by project maintainers.
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting Ryan Scherler at ryan@eastslope.studio. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
reported by contacting Dan Harrin at [dan@danharrin.com](mailto:dan@danharrin.com).
|
||||
All complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
@@ -67,10 +67,7 @@ members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html), version 1.4.
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
[the FAQs](https://www.contributor-covenant.org/faq).
|
||||
|
||||
@@ -75,51 +75,7 @@ composer require filament/widgets
|
||||
|
||||
## Contributing
|
||||
|
||||
If you want to contribute to the Filament packages, then you may want to test it in a real Laravel project:
|
||||
|
||||
- Fork this repository to your GitHub account.
|
||||
- Create a Laravel app locally.
|
||||
- Clone your fork in your Laravel app's root directory.
|
||||
- In the `/filament` directory, create a branch for your fix, e.g. `fix/error-message`.
|
||||
|
||||
Install the packages in your app's `composer.json`:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
// ...
|
||||
"require": {
|
||||
"filament/filament": "*",
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"repositories": [
|
||||
{
|
||||
"type": "path",
|
||||
"url": "filament/packages/*"
|
||||
}
|
||||
],
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Now, run `composer update`.
|
||||
|
||||
### Checking for missing translations
|
||||
|
||||
Set up a Laravel app, and install the [admin panel](https://filamentphp.com/docs/admin/installation).
|
||||
|
||||
Now, if you want to check for missing Spanish translations, run:
|
||||
|
||||
```bash
|
||||
php artisan filament:check-translations es
|
||||
```
|
||||
|
||||
This will let you know which translations are missing for this locale. You can make a PR with the changes to this repository.
|
||||
|
||||
If you've published the translations into your app and you'd like to check those instead, try:
|
||||
|
||||
```bash
|
||||
php artisan filament:check-translations es --source=app
|
||||
```
|
||||
Please see our [contributing guide](https://filamentphp.com/docs/support/contributing).
|
||||
|
||||
## Need Help?
|
||||
|
||||
|
||||
+2
-1
@@ -26,7 +26,6 @@ $app = (new Application($basePathLocator::applicationBasePath()))
|
||||
->createApplication();
|
||||
|
||||
$app->register(LivewireServiceProvider::class);
|
||||
$app->register(AdminPanelProvider::class);
|
||||
$app->register(FilamentServiceProvider::class);
|
||||
$app->register(ActionsServiceProvider::class);
|
||||
$app->register(FormsServiceProvider::class);
|
||||
@@ -37,4 +36,6 @@ $app->register(SupportServiceProvider::class);
|
||||
$app->register(TablesServiceProvider::class);
|
||||
$app->register(WidgetsServiceProvider::class);
|
||||
|
||||
$app->register(AdminPanelProvider::class);
|
||||
|
||||
return $app;
|
||||
|
||||
Generated
+302
-337
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Generated
+159
-133
@@ -274,9 +274,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-create-class-features-plugin": {
|
||||
"version": "7.23.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.9.tgz",
|
||||
"integrity": "sha512-B2L9neXTIyPQoXDm+NtovPvG6VOLWnaXu3BIeVDWwdKFgG30oNa6CqVGiJPDWQwIAK49t9gnQI9c6K6RzabiKw==",
|
||||
"version": "7.23.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.10.tgz",
|
||||
"integrity": "sha512-2XpP2XhkXzgxecPNEEK8Vz8Asj9aRxt08oKOqtiZoqV2UGZ5T+EkyP9sXQ9nwMxBIG34a7jmasVqoMop7VdPUw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-annotate-as-pure": "^7.22.5",
|
||||
@@ -2743,9 +2743,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/express/node_modules/@types/express-serve-static-core": {
|
||||
"version": "4.17.42",
|
||||
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.42.tgz",
|
||||
"integrity": "sha512-ckM3jm2bf/MfB3+spLPWYPUH573plBFwpOhqQ2WottxYV85j1HQFlxmnTq57X1yHY9awZPig06hL/cLMgNWHIQ==",
|
||||
"version": "4.17.43",
|
||||
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz",
|
||||
"integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
@@ -2850,9 +2850,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.7.tgz",
|
||||
"integrity": "sha512-GPmeN1C3XAyV5uybAf4cMLWT9fDWcmQhZVtMFu7OR32WjrqGG+Wnk2V1d0bmtUyE/Zy1QJ9BxyiTih9z8Oks8A==",
|
||||
"version": "20.11.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz",
|
||||
"integrity": "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
@@ -3344,13 +3344,16 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/array-buffer-byte-length": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
|
||||
"integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz",
|
||||
"integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.2",
|
||||
"is-array-buffer": "^3.0.1"
|
||||
"call-bind": "^1.0.5",
|
||||
"is-array-buffer": "^3.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -3372,17 +3375,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/arraybuffer.prototype.slice": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz",
|
||||
"integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz",
|
||||
"integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"array-buffer-byte-length": "^1.0.0",
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.2.0",
|
||||
"es-abstract": "^1.22.1",
|
||||
"get-intrinsic": "^1.2.1",
|
||||
"is-array-buffer": "^3.0.2",
|
||||
"array-buffer-byte-length": "^1.0.1",
|
||||
"call-bind": "^1.0.5",
|
||||
"define-properties": "^1.2.1",
|
||||
"es-abstract": "^1.22.3",
|
||||
"es-errors": "^1.2.1",
|
||||
"get-intrinsic": "^1.2.3",
|
||||
"is-array-buffer": "^3.0.4",
|
||||
"is-shared-array-buffer": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3479,9 +3483,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/available-typed-arrays": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
|
||||
"integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==",
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.6.tgz",
|
||||
"integrity": "sha512-j1QzY8iPNPG4o4xmO3ptzpRxTciqD3MgEHtifP/YnJpIo58Xu+ne4BejlbkuaLfXn/nz6HFiw29bLpj2PNMdGg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -3817,9 +3821,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.22.2",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz",
|
||||
"integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==",
|
||||
"version": "4.22.3",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz",
|
||||
"integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -3836,8 +3840,8 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001565",
|
||||
"electron-to-chromium": "^1.4.601",
|
||||
"caniuse-lite": "^1.0.30001580",
|
||||
"electron-to-chromium": "^1.4.648",
|
||||
"node-releases": "^2.0.14",
|
||||
"update-browserslist-db": "^1.0.13"
|
||||
},
|
||||
@@ -3887,14 +3891,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz",
|
||||
"integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==",
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.6.tgz",
|
||||
"integrity": "sha512-Mj50FLHtlsoVfRfnHaZvyrooHcrlceNZdL/QBvJJVd9Ta55qCQK0gs4ss2oZDeV9zFCs6ewzYgVE5yfVmfFpVg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.1",
|
||||
"set-function-length": "^1.1.1"
|
||||
"get-intrinsic": "^1.2.3",
|
||||
"set-function-length": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -3941,9 +3949,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001580",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001580.tgz",
|
||||
"integrity": "sha512-mtj5ur2FFPZcCEpXFy8ADXbDACuNFXg6mxVDqp7tqooX6l3zwm+d8EPoeOSIFRDvHs8qu7/SLFOGniULkcH2iA==",
|
||||
"version": "1.0.30001585",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001585.tgz",
|
||||
"integrity": "sha512-yr2BWR1yLXQ8fMpdS/4ZZXpseBgE7o4g41x3a6AJOqZuOi+iE/WdJYAuZ6Y95i4Ohd2Y+9MzIWRR+uGABH4s3Q==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -4013,16 +4021,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
||||
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
@@ -4035,6 +4037,9 @@
|
||||
"engines": {
|
||||
"node": ">= 8.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
@@ -4725,14 +4730,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/define-data-property": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz",
|
||||
"integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==",
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.2.tgz",
|
||||
"integrity": "sha512-SRtsSqsDbgpJBbW3pABMCOt6rQyeM8s8RiyeSN8jYG8sYmt/kGJejbydttUsnDs1tadr19tvhT4ShwMyoqAm4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.2",
|
||||
"gopd": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.0"
|
||||
"has-property-descriptors": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -4998,9 +5004,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.647",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.647.tgz",
|
||||
"integrity": "sha512-Z/fTNGwc45WrYQhPaEcz5tAJuZZ8G7S/DBnhS6Kgp4BxnS40Z/HqlJ0hHg3Z79IGVzuVartIlTcjw/cQbPLgOw==",
|
||||
"version": "1.4.659",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.659.tgz",
|
||||
"integrity": "sha512-sRJ3nV3HowrYpBtPF9bASQV7OW49IgZC01Xiq43WfSE3RTCkK0/JidoCmR73Hyc1mN+l/H4Yqx0eNiomvExFZg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/elliptic": {
|
||||
@@ -5071,9 +5077,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/envinfo": {
|
||||
"version": "7.11.0",
|
||||
"resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz",
|
||||
"integrity": "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==",
|
||||
"version": "7.11.1",
|
||||
"resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.1.tgz",
|
||||
"integrity": "sha512-8PiZgZNIB4q/Lw4AhOvAfB/ityHAd2bli3lESSWmWSzSsl5dKpy5N1d1Rfkd2teq/g9xN90lc6o98DOjMeYHpg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"envinfo": "dist/cli.js"
|
||||
@@ -5144,6 +5150,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz",
|
||||
@@ -5232,9 +5247,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
|
||||
"integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -5533,9 +5548,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.16.0",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz",
|
||||
"integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==",
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
|
||||
"integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"reusify": "^1.0.4"
|
||||
@@ -6022,16 +6037,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz",
|
||||
"integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==",
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
@@ -6049,13 +6068,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/get-symbol-description": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
|
||||
"integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.1.tgz",
|
||||
"integrity": "sha512-KmuibvwbWaM4BHcBRYwJfZ1JxyJeBwB8ct9YYu67SvYdbEIlcQ2e56dHxfbobqW38GXo8/zDFqJeGtHiVbWyQw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.2",
|
||||
"get-intrinsic": "^1.1.1"
|
||||
"call-bind": "^1.0.5",
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -6230,12 +6249,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
|
||||
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.2"
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -6597,9 +6616,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz",
|
||||
"integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==",
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
|
||||
"integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
@@ -6716,12 +6735,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/internal-slot": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz",
|
||||
"integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==",
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz",
|
||||
"integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"hasown": "^2.0.0",
|
||||
"side-channel": "^1.0.4"
|
||||
},
|
||||
@@ -6748,14 +6767,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/is-array-buffer": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
|
||||
"integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==",
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
|
||||
"integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.2",
|
||||
"get-intrinsic": "^1.2.0",
|
||||
"is-typed-array": "^1.1.10"
|
||||
"get-intrinsic": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -7028,12 +7049,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/is-typed-array": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz",
|
||||
"integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==",
|
||||
"version": "1.1.13",
|
||||
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz",
|
||||
"integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"which-typed-array": "^1.1.11"
|
||||
"which-typed-array": "^1.1.14"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -8513,9 +8534,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.33",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
|
||||
"integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==",
|
||||
"version": "8.4.34",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.34.tgz",
|
||||
"integrity": "sha512-4eLTO36woPSocqZ1zIrFD2K1v6wH7pY1uBh0JIM2KKfrVtGvPFiAku6aNOP0W1Wr9qwnaCsF0Z+CrVnryB2A8Q==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -9204,9 +9225,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz",
|
||||
"integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==",
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
|
||||
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
@@ -9880,13 +9901,13 @@
|
||||
]
|
||||
},
|
||||
"node_modules/safe-regex-test": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.2.tgz",
|
||||
"integrity": "sha512-83S9w6eFq12BBIJYvjMux6/dkirb8+4zJRA9cxNBVb7Wq5fJBW+Xze48WqR8pxua7bDuAaaAxtVVd4Idjp1dBQ==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz",
|
||||
"integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.5",
|
||||
"get-intrinsic": "^1.2.2",
|
||||
"call-bind": "^1.0.6",
|
||||
"es-errors": "^1.3.0",
|
||||
"is-regex": "^1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
@@ -9940,9 +9961,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
|
||||
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
@@ -10120,14 +10141,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz",
|
||||
"integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz",
|
||||
"integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"define-data-property": "^1.1.1",
|
||||
"define-data-property": "^1.1.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.2",
|
||||
"get-intrinsic": "^1.2.3",
|
||||
"gopd": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.1"
|
||||
},
|
||||
@@ -10223,14 +10245,18 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.5.tgz",
|
||||
"integrity": "sha512-QcgiIWV4WV7qWExbN5llt6frQB/lBven9pqliLXfGPB+K9ZYXxDozp0wLkHS24kWCm+6YXH/f0HhnObZnZOBnQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.0",
|
||||
"get-intrinsic": "^1.0.2",
|
||||
"object-inspect": "^1.9.0"
|
||||
"call-bind": "^1.0.6",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"object-inspect": "^1.13.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -11070,14 +11096,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typed-array-buffer": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz",
|
||||
"integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.1.tgz",
|
||||
"integrity": "sha512-RSqu1UEuSlrBhHTWC8O9FnPjOduNs4M7rJ4pRKoEjtx1zUNOPN2sSXHLDX+Y2WPbHIxbvg4JFo2DNAEfPIKWoQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.2",
|
||||
"get-intrinsic": "^1.2.1",
|
||||
"is-typed-array": "^1.1.10"
|
||||
"call-bind": "^1.0.6",
|
||||
"es-errors": "^1.3.0",
|
||||
"is-typed-array": "^1.1.13"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -11413,9 +11439,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.90.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.0.tgz",
|
||||
"integrity": "sha512-bdmyXRCXeeNIePv6R6tGPyy20aUobw4Zy8r0LUS2EWO+U+Ke/gYDgsCh7bl5rB6jPpr4r0SZa6dPxBxLooDT3w==",
|
||||
"version": "5.90.1",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.1.tgz",
|
||||
"integrity": "sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/eslint-scope": "^3.7.3",
|
||||
@@ -11892,16 +11918,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/which-typed-array": {
|
||||
"version": "1.1.13",
|
||||
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz",
|
||||
"integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==",
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz",
|
||||
"integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"available-typed-arrays": "^1.0.5",
|
||||
"call-bind": "^1.0.4",
|
||||
"available-typed-arrays": "^1.0.6",
|
||||
"call-bind": "^1.0.5",
|
||||
"for-each": "^0.3.3",
|
||||
"gopd": "^1.0.1",
|
||||
"has-tostringtag": "^1.0.0"
|
||||
"has-tostringtag": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"illuminate/contracts": "^10.0",
|
||||
"illuminate/database": "^10.0",
|
||||
"illuminate/support": "^10.0",
|
||||
"league/csv": "9.11.0",
|
||||
"league/csv": "^9.14",
|
||||
"openspout/openspout": "^4.23",
|
||||
"spatie/laravel-package-tools": "^1.9"
|
||||
},
|
||||
|
||||
@@ -446,6 +446,15 @@ ImportColumn::make('sku')
|
||||
->example('ABC123')
|
||||
```
|
||||
|
||||
By default, the name of the column is used in the header of the example CSV. You can customize the header per-column using `exampleHeader()`:
|
||||
|
||||
```php
|
||||
use Filament\Actions\Imports\ImportColumn;
|
||||
|
||||
ImportColumn::make('sku')
|
||||
->exampleHeader('SKU')
|
||||
```
|
||||
|
||||
## Using a custom user model
|
||||
|
||||
By default, the `imports` table has a `user_id` column. That column is constrained to the `users` table:
|
||||
@@ -603,6 +612,17 @@ public function getJobTags(): array
|
||||
|
||||
If you'd like to customize the tags that are applied to jobs of a certain importer, you may override this method in your importer class.
|
||||
|
||||
### Customizing the import job batch name
|
||||
|
||||
By default, the import system doesn't define any name for the job batches. If you'd like to customize the name that is applied to job batches of a certain importer, you may override the `getJobBatchName()` method in your importer class:
|
||||
|
||||
```php
|
||||
public function getJobBatchName(): ?string
|
||||
{
|
||||
return 'product-import';
|
||||
}
|
||||
```
|
||||
|
||||
## Customizing import validation messages
|
||||
|
||||
The import system will automatically validate the CSV file before it is imported. If there are any errors, the user will be shown a list of them, and the import will not be processed. If you'd like to override any default validation messages, you may do so by overriding the `getValidationMessages()` method on your importer class:
|
||||
|
||||
@@ -283,7 +283,6 @@ By default, the export action will allow the user to choose between both CSV and
|
||||
|
||||
```php
|
||||
use App\Filament\Exports\ProductExporter;
|
||||
use Filament\Actions\ExportAction;
|
||||
use Filament\Actions\Exports\Enums\ExportFormat;
|
||||
|
||||
ExportAction::make()
|
||||
@@ -315,6 +314,19 @@ public function getFormats(): array
|
||||
}
|
||||
```
|
||||
|
||||
## Modifying the export query
|
||||
|
||||
By default, if you are using the `ExportAction` with a table, the action will use the table's currently filtered and sorted query to export the data. If you don't have a table, it will use the model's default query. To modify the query builder before exporting, you can use the `modifyQueryUsing()` method on the action:
|
||||
|
||||
```php
|
||||
use App\Filament\Exports\ProductExporter;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
ExportAction::make()
|
||||
->exporter(ProductExporter::class)
|
||||
->modifyQueryUsing(fn (Builder $query) => $query->where('is_active', true))
|
||||
```
|
||||
|
||||
## Configuring the export filesystem
|
||||
|
||||
### Customizing the storage disk
|
||||
@@ -324,8 +336,6 @@ By default, exported files will be uploaded to the storage disk defined in the [
|
||||
If you want to use a different disk for a specific export, you can pass the disk name to the `disk()` method on the action:
|
||||
|
||||
```php
|
||||
use Filament\Actions\ExportAction;
|
||||
|
||||
ExportAction::make()
|
||||
->exporter(ProductExporter::class)
|
||||
->fileDisk('s3')
|
||||
@@ -345,7 +355,6 @@ public function getFileDisk(): string
|
||||
By default, exported files will have a name generated based on the ID and type of the export. You can also use the `fileName()` method on the action to customize the file name:
|
||||
|
||||
```php
|
||||
use Filament\Actions\ExportAction;
|
||||
use Filament\Actions\Exports\Models\Export;
|
||||
|
||||
ExportAction::make()
|
||||
@@ -385,8 +394,6 @@ public static function getOptionsFormComponents(): array
|
||||
Alternatively, you can pass a set of static options to the exporter through the `options()` method on the action:
|
||||
|
||||
```php
|
||||
use Filament\Actions\ExportAction;
|
||||
|
||||
ExportAction::make()
|
||||
->exporter(ProductExporter::class)
|
||||
->options([
|
||||
@@ -572,6 +579,17 @@ public function getJobTags(): array
|
||||
|
||||
If you'd like to customize the tags that are applied to jobs of a certain exporter, you may override this method in your exporter class.
|
||||
|
||||
### Customizing the export job batch name
|
||||
|
||||
By default, the export system doesn't define any name for the job batches. If you'd like to customize the name that is applied to job batches of a certain exporter, you may override the `getJobBatchName()` method in your exporter class:
|
||||
|
||||
```php
|
||||
public function getJobBatchName(): ?string
|
||||
{
|
||||
return 'product-export';
|
||||
}
|
||||
```
|
||||
|
||||
## Authorization
|
||||
|
||||
By default, only the user who started the export may download files that get generated. If you'd like to customize the authorization logic, you may create an `ExportPolicy` class, and [register it in your `AuthServiceProvider`](https://laravel.com/docs/10.x/authorization#registering-policies):
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'label' => 'Exportovat :label',
|
||||
|
||||
'modal' => [
|
||||
|
||||
'heading' => 'Exportovat :label',
|
||||
|
||||
'form' => [
|
||||
|
||||
'columns' => [
|
||||
|
||||
'label' => 'Sloupce',
|
||||
|
||||
'form' => [
|
||||
|
||||
'is_enabled' => [
|
||||
'label' => ':column povoleno',
|
||||
],
|
||||
|
||||
'label' => [
|
||||
'label' => ':column popisek',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'actions' => [
|
||||
|
||||
'export' => [
|
||||
'label' => 'Exportovat',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'notifications' => [
|
||||
|
||||
'completed' => [
|
||||
|
||||
'title' => 'Export dokončen',
|
||||
|
||||
'actions' => [
|
||||
|
||||
'download_csv' => [
|
||||
'label' => 'Stáhnout .csv',
|
||||
],
|
||||
|
||||
'download_xlsx' => [
|
||||
'label' => 'Stáhnout .xlsx',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'max_rows' => [
|
||||
'title' => 'Export překračuje povolenou velikost',
|
||||
'body' => 'Není možné exportovat více než 1 řádek najednou.|Není možné exportovat více než :count řádků najednou.',
|
||||
],
|
||||
|
||||
'started' => [
|
||||
'title' => 'Export byl zahájen',
|
||||
'body' => 'Export byl zahájen a 1 řádek bude zpracován na pozadí.|Váš export byl zahájen a :count řádků bude zpracováno na pozadí.',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'file_name' => 'export-:export_id-:model',
|
||||
|
||||
];
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'label' => 'Exportiere :label',
|
||||
|
||||
'modal' => [
|
||||
|
||||
'heading' => 'Exportiere :label',
|
||||
|
||||
'form' => [
|
||||
|
||||
'columns' => [
|
||||
|
||||
'label' => 'Spalten',
|
||||
|
||||
'form' => [
|
||||
|
||||
'is_enabled' => [
|
||||
'label' => ':column ist aktiviert',
|
||||
],
|
||||
|
||||
'label' => [
|
||||
'label' => ':column Beschriftung',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'actions' => [
|
||||
|
||||
'export' => [
|
||||
'label' => 'Export',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'notifications' => [
|
||||
|
||||
'completed' => [
|
||||
|
||||
'title' => 'Export abgeschlossen',
|
||||
|
||||
'actions' => [
|
||||
|
||||
'download_csv' => [
|
||||
'label' => 'Lade .csv herunter',
|
||||
],
|
||||
|
||||
'download_xlsx' => [
|
||||
'label' => 'Lade .xlsx herunter',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'max_rows' => [
|
||||
'title' => 'Der Export ist zu groß',
|
||||
'body' => 'Nicht mehr als 1 Zeile auf einmal exportieren.|Nicht mehr als :count Zeilen auf einmal exportieren.',
|
||||
],
|
||||
|
||||
'started' => [
|
||||
'title' => 'Export gestartet',
|
||||
'body' => 'Der Export wurde gestartet und 1 Zeile wird im Hintergrund verarbeitet.| Der Export wurde gestartet und :count Zeilen werden im Hintergrund verarbeitet.',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'file_name' => 'export-:export_id-:model',
|
||||
|
||||
];
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'label' => ':label Import',
|
||||
|
||||
'modal' => [
|
||||
|
||||
'heading' => ':label Import',
|
||||
|
||||
'form' => [
|
||||
|
||||
'file' => [
|
||||
'label' => 'Datei',
|
||||
'placeholder' => 'CSV Datei hochladen',
|
||||
],
|
||||
|
||||
'columns' => [
|
||||
'label' => 'Spalten',
|
||||
'placeholder' => 'Spalte auswählen',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'actions' => [
|
||||
|
||||
'download_example' => [
|
||||
'label' => 'Lade Beispiel CSV herunter',
|
||||
],
|
||||
|
||||
'import' => [
|
||||
'label' => 'Importieren',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'notifications' => [
|
||||
|
||||
'completed' => [
|
||||
|
||||
'title' => 'Import abgeschlossen',
|
||||
|
||||
'actions' => [
|
||||
|
||||
'download_failed_rows_csv' => [
|
||||
'label' => 'Informationen über die fehlgeschlagene Zeile herunterladen|Informationen über die fehlgeschlagenen Zeilen herunterladen',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'max_rows' => [
|
||||
'title' => 'CSV Datei ist zu groß',
|
||||
'body' => 'Nicht mehr als 1 Zeile auf einmal importieren.|Nicht mehr als :count Zeilen auf einmal importieren.',
|
||||
],
|
||||
|
||||
'started' => [
|
||||
'title' => 'Import gestartet',
|
||||
'body' => 'Der Import wurde gestartet und 1 Zeile wird im Hintergrund verarbeitet.|Der Import wurde gestartet und :count Zeilen werden im Hintergrund verarbeitet.',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'example_csv' => [
|
||||
'file_name' => ':importer-example',
|
||||
],
|
||||
|
||||
'failure_csv' => [
|
||||
'file_name' => 'import-:import_id-:csv_name-failed-rows',
|
||||
'error_header' => 'Fehler',
|
||||
'system_error' => 'Systemfehler, bitte Supportanfrage stellen.',
|
||||
],
|
||||
|
||||
];
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'label' => 'Eksport :label',
|
||||
|
||||
'modal' => [
|
||||
|
||||
'heading' => 'Eksport :label',
|
||||
|
||||
'form' => [
|
||||
|
||||
'columns' => [
|
||||
|
||||
'label' => 'Kolum',
|
||||
|
||||
'form' => [
|
||||
|
||||
'is_enabled' => [
|
||||
'label' => ':column diaktifkan',
|
||||
],
|
||||
|
||||
'label' => [
|
||||
'label' => ':column label',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'actions' => [
|
||||
|
||||
'export' => [
|
||||
'label' => 'Eksport',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'notifications' => [
|
||||
|
||||
'completed' => [
|
||||
|
||||
'title' => 'Eksport selesai',
|
||||
|
||||
'actions' => [
|
||||
|
||||
'download_csv' => [
|
||||
'label' => 'Muat turun .csv',
|
||||
],
|
||||
|
||||
'download_xlsx' => [
|
||||
'label' => 'Muat turun .xlsx',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'max_rows' => [
|
||||
'title' => 'Eksport terlalu besar',
|
||||
'body' => 'Anda tidak boleh mengeluarkan lebih dari 1 baris pada satu masa.|Anda tidak boleh mengeluarkan lebih dari :count baris pada satu masa.',
|
||||
],
|
||||
|
||||
'started' => [
|
||||
'title' => 'Eksport bermula',
|
||||
'body' => 'Eksport anda telah bermula dan 1 baris akan diproses di belakang tabir.|Eksport anda telah bermula dan :count baris akan diproses di belakang tabir.',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'file_name' => 'eksport-:export_id-:model',
|
||||
|
||||
];
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'label' => 'Eksportuj :label',
|
||||
|
||||
'modal' => [
|
||||
|
||||
'heading' => 'Eksportuj :label',
|
||||
|
||||
'form' => [
|
||||
|
||||
'columns' => [
|
||||
|
||||
'label' => 'Kolumny',
|
||||
|
||||
'form' => [
|
||||
|
||||
'is_enabled' => [
|
||||
'label' => ':column włączony',
|
||||
],
|
||||
|
||||
'label' => [
|
||||
'label' => 'Etykieta :column',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'actions' => [
|
||||
|
||||
'export' => [
|
||||
'label' => 'Eksportuj',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'notifications' => [
|
||||
|
||||
'completed' => [
|
||||
|
||||
'title' => 'Eksport zakończony',
|
||||
|
||||
'actions' => [
|
||||
|
||||
'download_csv' => [
|
||||
'label' => 'Pobierz .csv',
|
||||
],
|
||||
|
||||
'download_xlsx' => [
|
||||
'label' => 'Pobierz .xlsx',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'max_rows' => [
|
||||
'title' => 'Zbyt dużo wierszy',
|
||||
'body' => 'Nie możesz wyeksportować więcej niż 1 wiersz na raz.|Nie możesz wyeksportować więcej niż :count wierszy na raz.',
|
||||
],
|
||||
|
||||
'started' => [
|
||||
'title' => 'Eksport rozpoczęty',
|
||||
'body' => 'Rozpoczęto eksport i 1 wiersz zostanie przetworzony w tle.|Rozpoczęto eksport i :count wierszy zostanie przetworzonych w tle.',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'file_name' => 'eksport-:export_id-:model',
|
||||
|
||||
];
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'label' => 'Importuj :label',
|
||||
|
||||
'modal' => [
|
||||
|
||||
'heading' => 'Importuj :label',
|
||||
|
||||
'form' => [
|
||||
|
||||
'file' => [
|
||||
'label' => 'Plik',
|
||||
'placeholder' => 'Wybierz plik CSV',
|
||||
],
|
||||
|
||||
'columns' => [
|
||||
'label' => 'Kolumny',
|
||||
'placeholder' => 'Wybierz kolumnę',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'actions' => [
|
||||
|
||||
'download_example' => [
|
||||
'label' => 'Pobierz przykładowy plik CSV',
|
||||
],
|
||||
|
||||
'import' => [
|
||||
'label' => 'Importuj',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'notifications' => [
|
||||
|
||||
'completed' => [
|
||||
|
||||
'title' => 'Import zakończony',
|
||||
|
||||
'actions' => [
|
||||
|
||||
'download_failed_rows_csv' => [
|
||||
'label' => 'Pobierz informacje o niepowodzeniach',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'max_rows' => [
|
||||
'title' => 'Przesłany plik CSV jest zbyt duży',
|
||||
'body' => 'Nie możesz zaimportować więcej niż 1 wiersz na raz.|Nie możesz zaimportować więcej niż :count wierszy na raz.',
|
||||
],
|
||||
|
||||
'started' => [
|
||||
'title' => 'Import rozpoczęty',
|
||||
'body' => 'Import rozpoczęty i 1 wiersz zostanie przetworzony w tle.|Import rozpoczęty i :count wierszy zostanie przetworzonych w tle.',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'example_csv' => [
|
||||
'file_name' => ':importer-example',
|
||||
],
|
||||
|
||||
'failure_csv' => [
|
||||
'file_name' => 'import-:import_id-:csv_name-failed-rows',
|
||||
'error_header' => 'błąd',
|
||||
'system_error' => 'Błąd systemu, skontaktuj się z pomocą techniczną.',
|
||||
],
|
||||
|
||||
];
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'label' => 'Exportă :label',
|
||||
|
||||
'modal' => [
|
||||
|
||||
'heading' => 'Exportă :label',
|
||||
|
||||
'form' => [
|
||||
|
||||
'columns' => [
|
||||
|
||||
'label' => 'Coloane',
|
||||
|
||||
'form' => [
|
||||
|
||||
'is_enabled' => [
|
||||
'label' => ':column activat',
|
||||
],
|
||||
|
||||
'label' => [
|
||||
'label' => 'Eticheta :column',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'actions' => [
|
||||
|
||||
'export' => [
|
||||
'label' => 'Exportă',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'notifications' => [
|
||||
|
||||
'completed' => [
|
||||
|
||||
'title' => 'Export complet',
|
||||
|
||||
'actions' => [
|
||||
|
||||
'download_csv' => [
|
||||
'label' => 'Descarcă .csv',
|
||||
],
|
||||
|
||||
'download_xlsx' => [
|
||||
'label' => 'Descarcă .xlsx',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'max_rows' => [
|
||||
'title' => 'Exportul este prea mare',
|
||||
'body' => 'Nu puteți exporta mai mult de 1 rând odată.|Nu puteți exporta mai mult de :count rânduri odată',
|
||||
],
|
||||
|
||||
'started' => [
|
||||
'title' => 'Exportul a început',
|
||||
'body' => 'Exportul dvs. a început și 1 rând va fi procesat în fundal.|Exportul dvs. a început și :count rânduri vor fi procesate în fundal.',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'file_name' => 'export-:export_id-:model',
|
||||
|
||||
];
|
||||
@@ -17,7 +17,7 @@ return [
|
||||
],
|
||||
|
||||
'create_another' => [
|
||||
'label' => 'Oluştur ve başka bir tane oluştur',
|
||||
'label' => 'Oluştur & yeni oluştur',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'label' => 'Dışa Aktar :label',
|
||||
|
||||
'modal' => [
|
||||
|
||||
'heading' => 'Dışa Aktar :label',
|
||||
|
||||
'form' => [
|
||||
|
||||
'columns' => [
|
||||
|
||||
'label' => 'Sütunlar',
|
||||
|
||||
'form' => [
|
||||
|
||||
'is_enabled' => [
|
||||
'label' => ':column etkin',
|
||||
],
|
||||
|
||||
'label' => [
|
||||
'label' => ':column etiketi',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'actions' => [
|
||||
|
||||
'export' => [
|
||||
'label' => 'Dışa Aktar',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'notifications' => [
|
||||
|
||||
'completed' => [
|
||||
|
||||
'title' => 'Dışa Aktarım Tamamlandı',
|
||||
|
||||
'actions' => [
|
||||
|
||||
'download_csv' => [
|
||||
'label' => '.csv Olarak İndir',
|
||||
],
|
||||
|
||||
'download_xlsx' => [
|
||||
'label' => '.xlsx Olarak İndir',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'max_rows' => [
|
||||
'title' => 'Maksimum Satır Sayısı Aşıldı',
|
||||
'body' => 'Birden fazla satırı dışa aktaramazsınız.|:count satırı dışa aktaramazsınız.',
|
||||
],
|
||||
|
||||
'started' => [
|
||||
'title' => 'Dışa Aktarım Başladı',
|
||||
'body' => 'Dışa aktarım başladı ve 1 satır arka planda işlenecek.|Dışa aktarım başladı ve :count satır arka planda işlenecek.',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'file_name' => 'export-:export_id-:model',
|
||||
|
||||
];
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'label' => 'İçe Aktar: :label',
|
||||
|
||||
'modal' => [
|
||||
|
||||
'heading' => 'İçe Aktar: :label',
|
||||
|
||||
'form' => [
|
||||
|
||||
'file' => [
|
||||
'label' => 'Dosya',
|
||||
'placeholder' => 'Bir CSV dosyası seçin',
|
||||
],
|
||||
|
||||
'columns' => [
|
||||
'label' => 'Kolonlar',
|
||||
'placeholder' => 'Kolonları eşleştirin',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'actions' => [
|
||||
|
||||
'download_example' => [
|
||||
'label' => 'Örnek CSV Dosyasını İndir',
|
||||
],
|
||||
|
||||
'import' => [
|
||||
'label' => 'İçe Aktar',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'notifications' => [
|
||||
|
||||
'completed' => [
|
||||
|
||||
'title' => 'İçe Aktarım Tamamlandı',
|
||||
|
||||
'actions' => [
|
||||
|
||||
'download_failed_rows_csv' => [
|
||||
'label' => 'Başarısız satır hakkında bilgileri indir|Başarısız satırlar hakkında bilgileri indir',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'max_rows' => [
|
||||
'title' => 'Yüklenen Dosya Çok Büyük',
|
||||
'body' => 'Aynı anda 1\'den fazla satır içeren dosyaları içe aktaramazsınız.|Aynı anda :count\'den fazla satır içeren dosyaları içe aktaramazsınız.',
|
||||
],
|
||||
|
||||
'started' => [
|
||||
'title' => 'İçe Aktarım Başladı',
|
||||
'body' => 'İçe aktarım başladı ve 1 satır arka planda işlenecek.|İçe aktarım başladı ve :count satır arka planda işlenecek.',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'example_csv' => [
|
||||
'file_name' => ':importer-example',
|
||||
],
|
||||
|
||||
'failure_csv' => [
|
||||
'file_name' => 'import-:import_id-:csv_name-failed-rows',
|
||||
'error_header' => 'hata',
|
||||
'system_error' => 'Sistem Hatası',
|
||||
],
|
||||
|
||||
];
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'label' => 'Xuất :label',
|
||||
|
||||
'modal' => [
|
||||
|
||||
'heading' => 'Xuất :label',
|
||||
|
||||
'form' => [
|
||||
|
||||
'columns' => [
|
||||
|
||||
'label' => 'Cột',
|
||||
|
||||
'form' => [
|
||||
|
||||
'is_enabled' => [
|
||||
'label' => ':column đã kích hoạt',
|
||||
],
|
||||
|
||||
'label' => [
|
||||
'label' => ':column nhãn',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'actions' => [
|
||||
|
||||
'export' => [
|
||||
'label' => 'Xuất',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'notifications' => [
|
||||
|
||||
'completed' => [
|
||||
|
||||
'title' => 'Xuất đã hoàn thành',
|
||||
|
||||
'actions' => [
|
||||
|
||||
'download_csv' => [
|
||||
'label' => 'Tải xuống .csv',
|
||||
],
|
||||
|
||||
'download_xlsx' => [
|
||||
'label' => 'Tải xuống .xlsx',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'max_rows' => [
|
||||
'title' => 'Xuất quá lớn',
|
||||
'body' => 'Bạn không thể xuất nhiều hơn 1 hàng cùng lúc.|Bạn không thể xuất nhiều hơn :count hàng cùng lúc.',
|
||||
],
|
||||
|
||||
'started' => [
|
||||
'title' => 'Xuất đã bắt đầu',
|
||||
'body' => 'Quá trình xuất của bạn đã bắt đầu và 1 hàng sẽ được xử lý ở nền.|Quá trình xuất của bạn đã bắt đầu và :count hàng sẽ được xử lý ở nền.',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'file_name' => 'export-:export_id-:model',
|
||||
|
||||
];
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'label' => '匯出 :label',
|
||||
|
||||
'modal' => [
|
||||
|
||||
'heading' => '匯出 :label',
|
||||
|
||||
'form' => [
|
||||
|
||||
'columns' => [
|
||||
|
||||
'label' => '欄位',
|
||||
|
||||
'form' => [
|
||||
|
||||
'is_enabled' => [
|
||||
'label' => ':column 開啟',
|
||||
],
|
||||
|
||||
'label' => [
|
||||
'label' => ':column 標籤',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'actions' => [
|
||||
|
||||
'export' => [
|
||||
'label' => '匯出',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'notifications' => [
|
||||
|
||||
'completed' => [
|
||||
|
||||
'title' => '匯出完成',
|
||||
|
||||
'actions' => [
|
||||
|
||||
'download_csv' => [
|
||||
'label' => '下載 .csv 檔案',
|
||||
],
|
||||
|
||||
'download_xlsx' => [
|
||||
'label' => '下載 .xlsx 檔案',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'max_rows' => [
|
||||
'title' => '匯出的檔案過大',
|
||||
'body' => '你不能一次匯出超過 1 行紀錄。|你不能一次匯出超過 :count 行紀錄。',
|
||||
],
|
||||
|
||||
'started' => [
|
||||
'title' => '匯出開始',
|
||||
'body' => '你的匯出已經開始,將在背景處理共 1 行。|你的匯出已經開始,將在背景處理共 :count 行。',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'file_name' => 'export-:export_id-:model',
|
||||
|
||||
];
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'label' => '匯入:label',
|
||||
|
||||
'modal' => [
|
||||
|
||||
'heading' => '匯入:label',
|
||||
|
||||
'form' => [
|
||||
|
||||
'file' => [
|
||||
'label' => '檔案',
|
||||
'placeholder' => '上傳一個 CSV 檔案',
|
||||
],
|
||||
|
||||
'columns' => [
|
||||
'label' => '欄位',
|
||||
'placeholder' => '選擇一個欄位',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'actions' => [
|
||||
|
||||
'download_example' => [
|
||||
'label' => '下載一個 CSV 模板',
|
||||
],
|
||||
|
||||
'import' => [
|
||||
'label' => '匯入',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'notifications' => [
|
||||
|
||||
'completed' => [
|
||||
|
||||
'title' => '匯入完成',
|
||||
|
||||
'actions' => [
|
||||
'download_failed_rows_csv' => [
|
||||
'label' => '下載匯入失敗的紀錄|下載匯入失敗的紀錄',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'max_rows' => [
|
||||
'title' => '上傳的 CSV 檔案過大',
|
||||
'body' => '你不能一次匯入超過 1 行紀錄。|你不能一次匯入超過 :count 行紀錄。',
|
||||
],
|
||||
|
||||
'started' => [
|
||||
'title' => '匯入開始',
|
||||
'body' => '你的匯入已經開始,將在後台處理共 1 行。|你的匯入已經開始,將在後台處理共 :count 行。',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'example_csv' => [
|
||||
'file_name' => ':importer-example',
|
||||
],
|
||||
|
||||
'failure_csv' => [
|
||||
'file_name' => 'import-:import_id-:csv_name-failed-rows',
|
||||
'error_header' => '錯誤',
|
||||
'system_error' => '系統錯誤,請聯繫官方。',
|
||||
],
|
||||
|
||||
];
|
||||
@@ -59,6 +59,8 @@ trait CanExportRecords
|
||||
*/
|
||||
protected array | Closure | null $formats = null;
|
||||
|
||||
protected ?Closure $modifyQueryUsing = null;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
@@ -107,6 +109,12 @@ trait CanExportRecords
|
||||
$query = $action->getExporter()::getModel()::query();
|
||||
}
|
||||
|
||||
if ($this->modifyQueryUsing) {
|
||||
$query = $this->evaluate($this->modifyQueryUsing, [
|
||||
'query' => $query,
|
||||
]) ?? $query;
|
||||
}
|
||||
|
||||
$records = $action instanceof ExportTableBulkAction ? $action->getRecords() : null;
|
||||
|
||||
$totalRows = $records ? $records->count() : $query->count();
|
||||
@@ -166,6 +174,7 @@ trait CanExportRecords
|
||||
$job = $action->getJob();
|
||||
$jobQueue = $exporter->getJobQueue();
|
||||
$jobConnection = $exporter->getJobConnection();
|
||||
$jobBatchName = $exporter->getJobBatchName();
|
||||
|
||||
// We do not want to send the loaded user relationship to the queue in job payloads,
|
||||
// in case it contains attributes that are not serializable, such as binary columns.
|
||||
@@ -194,6 +203,10 @@ trait CanExportRecords
|
||||
filled($jobConnection),
|
||||
fn (PendingBatch $batch) => $batch->onConnection($jobConnection),
|
||||
)
|
||||
->when(
|
||||
filled($jobBatchName),
|
||||
fn (PendingBatch $batch) => $batch->name($jobBatchName),
|
||||
)
|
||||
->allowFailures(),
|
||||
...(($hasXlsx && (! $hasCsv)) ? [$makeCreateXlsxFileJob()] : []),
|
||||
app(ExportCompletion::class, [
|
||||
@@ -367,4 +380,11 @@ trait CanExportRecords
|
||||
{
|
||||
return $this->evaluate($this->formats);
|
||||
}
|
||||
|
||||
public function modifyQueryUsing(?Closure $callback): static
|
||||
{
|
||||
$this->modifyQueryUsing = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,6 +238,10 @@ trait CanImportRecords
|
||||
filled($jobConnection = $importer->getJobConnection()),
|
||||
fn (PendingBatch $batch) => $batch->onConnection($jobConnection),
|
||||
)
|
||||
->when(
|
||||
filled($jobBatchName = $importer->getJobBatchName()),
|
||||
fn (PendingBatch $batch) => $batch->name($jobBatchName),
|
||||
)
|
||||
->finally(function () use ($import) {
|
||||
$import->touch('completed_at');
|
||||
|
||||
@@ -304,7 +308,7 @@ trait CanImportRecords
|
||||
}
|
||||
|
||||
$csv->insertOne(array_map(
|
||||
fn (ImportColumn $column): string => $column->getName(),
|
||||
fn (ImportColumn $column): string => $column->getExampleHeader(),
|
||||
$columns,
|
||||
));
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ class ExportColumn extends Component
|
||||
|
||||
protected bool | Closure $isEnabledByDefault = true;
|
||||
|
||||
protected string $evaluationIdentifier = 'column';
|
||||
|
||||
final public function __construct(string $name)
|
||||
{
|
||||
$this->name($name);
|
||||
@@ -82,7 +84,7 @@ class ExportColumn extends Component
|
||||
|
||||
public function getRecord(): ?Model
|
||||
{
|
||||
return $this->getExporter()->getRecord();
|
||||
return $this->getExporter()?->getRecord();
|
||||
}
|
||||
|
||||
public function getLabel(): ?string
|
||||
|
||||
@@ -105,6 +105,11 @@ abstract class Exporter
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getJobBatchName(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<ExportColumn>
|
||||
*/
|
||||
|
||||
@@ -111,6 +111,7 @@ class PrepareCsvExport implements ShouldQueue
|
||||
$chunkKeySize = $this->chunkSize * 10;
|
||||
|
||||
$baseQuery = $query->toBase();
|
||||
$baseQuery->distinct($qualifiedKeyName);
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
$baseQueryOrders = $baseQuery->orders ?? [];
|
||||
|
||||
@@ -52,6 +52,8 @@ class ImportColumn extends Component
|
||||
|
||||
protected mixed $example = null;
|
||||
|
||||
protected string | Closure | null $exampleHeader = null;
|
||||
|
||||
protected string | Closure | null $relationship = null;
|
||||
|
||||
/**
|
||||
@@ -66,6 +68,8 @@ class ImportColumn extends Component
|
||||
|
||||
protected string | Closure | null $validationAttribute = null;
|
||||
|
||||
protected string $evaluationIdentifier = 'column';
|
||||
|
||||
final public function __construct(string $name)
|
||||
{
|
||||
$this->name($name);
|
||||
@@ -108,6 +112,13 @@ class ImportColumn extends Component
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function exampleHeader(string | Closure | null $header): static
|
||||
{
|
||||
$this->exampleHeader = $header;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function requiredMapping(bool | Closure $condition = true): static
|
||||
{
|
||||
$this->isMappingRequired = $condition;
|
||||
@@ -286,6 +297,11 @@ class ImportColumn extends Component
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getExampleHeader(): string
|
||||
{
|
||||
return $this->evaluate($this->exampleHeader) ?? $this->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed>
|
||||
*/
|
||||
@@ -439,7 +455,7 @@ class ImportColumn extends Component
|
||||
|
||||
public function getRecord(): ?Model
|
||||
{
|
||||
return $this->getImporter()->getRecord();
|
||||
return $this->getImporter()?->getRecord();
|
||||
}
|
||||
|
||||
public function isMappingRequired(): bool
|
||||
|
||||
@@ -264,6 +264,11 @@ abstract class Importer
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getJobBatchName(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<ImportColumn>
|
||||
*/
|
||||
|
||||
+7
-7
File diff suppressed because one or more lines are too long
@@ -367,7 +367,7 @@ MorphToSelect::make('commentable')
|
||||
])
|
||||
```
|
||||
|
||||
> Many of the same options in the select field are available for `MorphToSelect`, including `searchable()`, `preload()`, `allowHtml()`, and `optionsLimit()`.
|
||||
> Many of the same options in the select field are available for `MorphToSelect`, including `searchable()`, `preload()`, `native()`, `allowHtml()`, and `optionsLimit()`.
|
||||
|
||||
## Allowing HTML in the option labels
|
||||
|
||||
|
||||
@@ -346,6 +346,8 @@ FileUpload::make('attachment')
|
||||
|
||||
When the form is submitted, a temporary file upload object will be returned instead of a permanently stored file path. This is perfect for temporary files like imported CSVs.
|
||||
|
||||
Please be aware that images, video and audio files will not show the stored file name in the form's preview, unless you use [`previewable(false)`](#previewing-files). This is due to a limitation with the FilePond preview plugin.
|
||||
|
||||
## Orienting images from their EXIF data
|
||||
|
||||
By default, FilePond will automatically orient images based on their EXIF data. If you wish to disable this behaviour, you can use the `orientImagesFromExif(false)` method:
|
||||
|
||||
@@ -50,6 +50,7 @@ export default function fileUploadFormComponent({
|
||||
isOpenable,
|
||||
isPreviewable,
|
||||
isReorderable,
|
||||
itemPanelAspectRatio,
|
||||
loadingIndicatorPosition,
|
||||
locale,
|
||||
maxFiles,
|
||||
@@ -119,6 +120,7 @@ export default function fileUploadFormComponent({
|
||||
minFileSize: minSize,
|
||||
styleButtonProcessItemPosition: uploadButtonPosition,
|
||||
styleButtonRemoveItemPosition: removeUploadedFileButtonPosition,
|
||||
styleItemPanelAspectRatio: itemPanelAspectRatio,
|
||||
styleLoadIndicatorPosition: loadingIndicatorPosition,
|
||||
stylePanelAspectRatio: panelAspectRatio,
|
||||
stylePanelLayout: panelLayout,
|
||||
@@ -350,7 +352,10 @@ export default function fileUploadFormComponent({
|
||||
options: {
|
||||
type: 'local',
|
||||
...(!uploadedFile.type ||
|
||||
/^image/.test(uploadedFile.type)
|
||||
(isPreviewable &&
|
||||
(/^audio/.test(uploadedFile.type) ||
|
||||
/^image/.test(uploadedFile.type) ||
|
||||
/^video/.test(uploadedFile.type)))
|
||||
? {}
|
||||
: {
|
||||
file: {
|
||||
|
||||
@@ -419,6 +419,31 @@ return [
|
||||
'placeholder' => 'كلمة مفتاحية جديدة',
|
||||
],
|
||||
|
||||
'text_input' => [
|
||||
|
||||
'actions' => [
|
||||
|
||||
'hide_password' => [
|
||||
'label' => 'إخفاء كلمة المرور',
|
||||
],
|
||||
|
||||
'show_password' => [
|
||||
'label' => 'عرض كلمة المرور',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'toggle_buttons' => [
|
||||
|
||||
'boolean' => [
|
||||
'true' => 'نعم',
|
||||
'false' => 'لا',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'wizard' => [
|
||||
|
||||
'actions' => [
|
||||
|
||||
@@ -70,6 +70,134 @@ return [
|
||||
|
||||
],
|
||||
|
||||
'file_upload' => [
|
||||
|
||||
'editor' => [
|
||||
|
||||
'actions' => [
|
||||
|
||||
'cancel' => [
|
||||
'label' => 'Abbrechen',
|
||||
],
|
||||
|
||||
'drag_crop' => [
|
||||
'label' => 'Drag Modus "beschneiden"',
|
||||
],
|
||||
|
||||
'drag_move' => [
|
||||
'label' => 'Drag Modus "verschieben"',
|
||||
],
|
||||
|
||||
'flip_horizontal' => [
|
||||
'label' => 'Bild horizontal spiegeln',
|
||||
],
|
||||
|
||||
'flip_vertical' => [
|
||||
'label' => 'Bild vertikal spiegeln',
|
||||
],
|
||||
|
||||
'move_down' => [
|
||||
'label' => 'Bild nach unten',
|
||||
],
|
||||
|
||||
'move_left' => [
|
||||
'label' => 'Bild nach links',
|
||||
],
|
||||
|
||||
'move_right' => [
|
||||
'label' => 'Bild nach rechts',
|
||||
],
|
||||
|
||||
'move_up' => [
|
||||
'label' => 'Bild nach oben',
|
||||
],
|
||||
|
||||
'reset' => [
|
||||
'label' => 'Zurücksetzen',
|
||||
],
|
||||
|
||||
'rotate_left' => [
|
||||
'label' => 'Bild nach links kippen',
|
||||
],
|
||||
|
||||
'rotate_right' => [
|
||||
'label' => 'Bild nach rechts kippen',
|
||||
],
|
||||
|
||||
'set_aspect_ratio' => [
|
||||
'label' => 'Seitenverhältnis festlegen :ratio',
|
||||
],
|
||||
|
||||
'save' => [
|
||||
'label' => 'Speichern',
|
||||
],
|
||||
|
||||
'zoom_100' => [
|
||||
'label' => 'Bild Zoom 100%',
|
||||
],
|
||||
|
||||
'zoom_in' => [
|
||||
'label' => 'Hereinzoomen',
|
||||
],
|
||||
|
||||
'zoom_out' => [
|
||||
'label' => 'Herauszoomen',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'fields' => [
|
||||
|
||||
'height' => [
|
||||
'label' => 'Höhe',
|
||||
'unit' => 'px',
|
||||
],
|
||||
|
||||
'rotation' => [
|
||||
'label' => 'Drehung',
|
||||
'unit' => 'deg',
|
||||
],
|
||||
|
||||
'width' => [
|
||||
'label' => 'Breite',
|
||||
'unit' => 'px',
|
||||
],
|
||||
|
||||
'x_position' => [
|
||||
'label' => 'X',
|
||||
'unit' => 'px',
|
||||
],
|
||||
|
||||
'y_position' => [
|
||||
'label' => 'Y',
|
||||
'unit' => 'px',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'aspect_ratios' => [
|
||||
|
||||
'label' => 'Seitenverhältnisse',
|
||||
|
||||
'no_fixed' => [
|
||||
'label' => 'Frei',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'svg' => [
|
||||
|
||||
'messages' => [
|
||||
'confirmation' => 'Das Bearbeiten von SVG Dateien ist nicht empfohlen, da es Qualitätsverluste beim Verändern der Größe geben kann.\n Wirklich fortfahren?',
|
||||
'disabled' => 'Das Bearbeiten von SVG Dateien ist deaktiviert, da es Qualitätsverluste beim Verändern der Größe geben kann.',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'key_value' => [
|
||||
|
||||
'actions' => [
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'distinct' => [
|
||||
'must_be_selected' => 'Wenigstens muss das :attribute ausgewählt sein.',
|
||||
'only_one_must_be_selected' => 'Nur das :attribute Feld darf ausgewählt sein.',
|
||||
],
|
||||
|
||||
];
|
||||
@@ -419,6 +419,31 @@ return [
|
||||
'placeholder' => 'Nouveau tag',
|
||||
],
|
||||
|
||||
'text_input' => [
|
||||
|
||||
'actions' => [
|
||||
|
||||
'hide_password' => [
|
||||
'label' => 'Masquer le mot de passe',
|
||||
],
|
||||
|
||||
'show_password' => [
|
||||
'label' => 'Montrer le mot de passe',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'toggle_buttons' => [
|
||||
|
||||
'boolean' => [
|
||||
'true' => 'Oui',
|
||||
'false' => 'Non',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'wizard' => [
|
||||
|
||||
'actions' => [
|
||||
|
||||
@@ -419,6 +419,31 @@ return [
|
||||
'placeholder' => 'Tag baru',
|
||||
],
|
||||
|
||||
'text_input' => [
|
||||
|
||||
'actions' => [
|
||||
|
||||
'hide_password' => [
|
||||
'label' => 'Sembunyikan kata laluan',
|
||||
],
|
||||
|
||||
'show_password' => [
|
||||
'label' => 'Tunjukkan kata laluan',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'toggle_buttons' => [
|
||||
|
||||
'boolean' => [
|
||||
'true' => 'Ya',
|
||||
'false' => 'Tidak',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'wizard' => [
|
||||
|
||||
'actions' => [
|
||||
|
||||
@@ -345,7 +345,7 @@ return [
|
||||
],
|
||||
|
||||
'create_another' => [
|
||||
'label' => 'Oluştur & başka bir tane oluştur',
|
||||
'label' => 'Oluştur & yeni oluştur',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -419,6 +419,31 @@ return [
|
||||
'placeholder' => 'Thêm thẻ mới',
|
||||
],
|
||||
|
||||
'text_input' => [
|
||||
|
||||
'actions' => [
|
||||
|
||||
'hide_password' => [
|
||||
'label' => 'Ẩn mật khẩu',
|
||||
],
|
||||
|
||||
'show_password' => [
|
||||
'label' => 'Hiện mật khẩu',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'toggle_buttons' => [
|
||||
|
||||
'boolean' => [
|
||||
'true' => 'Có',
|
||||
'false' => 'Không',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'wizard' => [
|
||||
|
||||
'actions' => [
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
}
|
||||
|
||||
$nextTick(() => {
|
||||
error = $el.querySelector(\'[data-validation-error]\')
|
||||
let error = $el.querySelector(\'[data-validation-error]\')
|
||||
|
||||
if (! error) {
|
||||
return
|
||||
}
|
||||
|
||||
elementToExpand = error
|
||||
let elementToExpand = error
|
||||
|
||||
while (elementToExpand) {
|
||||
elementToExpand.dispatchEvent(new CustomEvent(\'expand\'))
|
||||
|
||||
@@ -95,22 +95,31 @@
|
||||
>
|
||||
@if ($isReorderableWithDragAndDrop || $isReorderableWithButtons || $hasBlockLabels || $isCloneable || $isDeletable || $isCollapsible || count($visibleExtraItemActions))
|
||||
<div
|
||||
class="fi-fo-builder-item-header flex items-center gap-x-3 overflow-hidden px-4 py-3"
|
||||
@if ($isCollapsible)
|
||||
x-on:click.stop="isCollapsed = !isCollapsed"
|
||||
@endif
|
||||
@class([
|
||||
'fi-fo-builder-item-header flex items-center gap-x-3 overflow-hidden px-4 py-3',
|
||||
'cursor-pointer select-none' => $isCollapsible,
|
||||
])
|
||||
>
|
||||
@if ($isReorderableWithDragAndDrop || $isReorderableWithButtons)
|
||||
<ul class="flex items-center gap-x-3">
|
||||
@if ($isReorderableWithDragAndDrop)
|
||||
<li x-sortable-handle>
|
||||
<li
|
||||
x-sortable-handle
|
||||
x-on:click.stop
|
||||
>
|
||||
{{ $reorderAction }}
|
||||
</li>
|
||||
@endif
|
||||
|
||||
@if ($isReorderableWithButtons)
|
||||
<li>
|
||||
<li x-on:click.stop>
|
||||
{{ $moveUpAction(['item' => $uuid])->disabled($loop->first) }}
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<li x-on:click.stop>
|
||||
{{ $moveDownAction(['item' => $uuid])->disabled($loop->last) }}
|
||||
</li>
|
||||
@endif
|
||||
@@ -119,13 +128,9 @@
|
||||
|
||||
@if ($hasBlockLabels)
|
||||
<h4
|
||||
@if ($isCollapsible)
|
||||
x-on:click.stop="isCollapsed = !isCollapsed"
|
||||
@endif
|
||||
@class([
|
||||
'text-sm font-medium text-gray-950 dark:text-white',
|
||||
'truncate' => $isBlockLabelTruncated(),
|
||||
'cursor-pointer select-none' => $isCollapsible,
|
||||
])
|
||||
>
|
||||
{{ $item->getParentComponent()->getLabel($item->getRawState(), $uuid) }}
|
||||
@@ -141,19 +146,19 @@
|
||||
class="ms-auto flex items-center gap-x-3"
|
||||
>
|
||||
@foreach ($visibleExtraItemActions as $extraItemAction)
|
||||
<li>
|
||||
<li x-on:click.stop>
|
||||
{{ $extraItemAction(['item' => $uuid]) }}
|
||||
</li>
|
||||
@endforeach
|
||||
|
||||
@if ($isCloneable)
|
||||
<li>
|
||||
<li x-on:click.stop>
|
||||
{{ $cloneAction(['item' => $uuid]) }}
|
||||
</li>
|
||||
@endif
|
||||
|
||||
@if ($isDeletable)
|
||||
<li>
|
||||
<li x-on:click.stop>
|
||||
{{ $deleteAction(['item' => $uuid]) }}
|
||||
</li>
|
||||
@endif
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
isOpenable: @js($isOpenable()),
|
||||
isPreviewable: @js($isPreviewable()),
|
||||
isReorderable: @js($isReorderable()),
|
||||
itemPanelAspectRatio: @js($getItemPanelAspectRatio()),
|
||||
loadingIndicatorPosition: @js($getLoadingIndicatorPosition()),
|
||||
locale: @js(app()->getLocale()),
|
||||
panelAspectRatio: @js($getPanelAspectRatio()),
|
||||
|
||||
@@ -95,24 +95,35 @@
|
||||
>
|
||||
@if ($isReorderableWithDragAndDrop || $isReorderableWithButtons || filled($itemLabel) || $isCloneable || $isDeletable || $isCollapsible || count($visibleExtraItemActions))
|
||||
<div
|
||||
class="fi-fo-repeater-item-header flex items-center gap-x-3 overflow-hidden px-4 py-3"
|
||||
@if ($isCollapsible)
|
||||
x-on:click.stop="isCollapsed = !isCollapsed"
|
||||
@endif
|
||||
@class([
|
||||
'fi-fo-repeater-item-header flex items-center gap-x-3 overflow-hidden px-4 py-3',
|
||||
'cursor-pointer select-none' => $isCollapsible,
|
||||
])
|
||||
>
|
||||
@if ($isReorderableWithDragAndDrop || $isReorderableWithButtons)
|
||||
<ul class="flex items-center gap-x-3">
|
||||
@if ($isReorderableWithDragAndDrop)
|
||||
<li x-sortable-handle>
|
||||
<li
|
||||
x-sortable-handle
|
||||
x-on:click.stop
|
||||
>
|
||||
{{ $reorderAction }}
|
||||
</li>
|
||||
@endif
|
||||
|
||||
@if ($isReorderableWithButtons)
|
||||
<li
|
||||
x-on:click.stop
|
||||
class="flex items-center justify-center"
|
||||
>
|
||||
{{ $moveUpAction(['item' => $uuid])->disabled($loop->first) }}
|
||||
</li>
|
||||
|
||||
<li
|
||||
x-on:click.stop
|
||||
class="flex items-center justify-center"
|
||||
>
|
||||
{{ $moveDownAction(['item' => $uuid])->disabled($loop->last) }}
|
||||
@@ -123,13 +134,9 @@
|
||||
|
||||
@if (filled($itemLabel))
|
||||
<h4
|
||||
@if ($isCollapsible)
|
||||
x-on:click.stop="isCollapsed = !isCollapsed"
|
||||
@endif
|
||||
@class([
|
||||
'text-sm font-medium text-gray-950 dark:text-white',
|
||||
'truncate' => $isItemLabelTruncated(),
|
||||
'cursor-pointer select-none' => $isCollapsible,
|
||||
])
|
||||
>
|
||||
{{ $itemLabel }}
|
||||
@@ -141,19 +148,19 @@
|
||||
class="ms-auto flex items-center gap-x-3"
|
||||
>
|
||||
@foreach ($visibleExtraItemActions as $extraItemAction)
|
||||
<li>
|
||||
<li x-on:click.stop>
|
||||
{{ $extraItemAction(['item' => $uuid]) }}
|
||||
</li>
|
||||
@endforeach
|
||||
|
||||
@if ($isCloneable)
|
||||
<li>
|
||||
<li x-on:click.stop>
|
||||
{{ $cloneAction(['item' => $uuid]) }}
|
||||
</li>
|
||||
@endif
|
||||
|
||||
@if ($isDeletable)
|
||||
<li>
|
||||
<li x-on:click.stop>
|
||||
{{ $deleteAction(['item' => $uuid]) }}
|
||||
</li>
|
||||
@endif
|
||||
|
||||
@@ -32,8 +32,10 @@
|
||||
:suffix-icon="$suffixIcon"
|
||||
:suffix-icon-color="$getSuffixIconColor()"
|
||||
:valid="! $errors->has($statePath)"
|
||||
class="fi-fo-select"
|
||||
:attributes="\Filament\Support\prepare_inherited_attributes($getExtraAttributeBag())"
|
||||
:attributes="
|
||||
\Filament\Support\prepare_inherited_attributes($getExtraAttributeBag())
|
||||
->class(['fi-fo-select'])
|
||||
"
|
||||
>
|
||||
@if ((! ($isSearchable() || $isMultiple()) && $isNative()))
|
||||
<x-filament::input.select
|
||||
@@ -141,7 +143,6 @@
|
||||
x-on:keydown.esc="select.dropdown.isActive && $event.stopPropagation()"
|
||||
{{
|
||||
$attributes
|
||||
->merge($getExtraAttributes(), escape: false)
|
||||
->merge($getExtraAlpineAttributes(), escape: false)
|
||||
->class([
|
||||
'[&_.choices\_\_inner]:ps-0' => $isPrefixInline && (count($prefixActions) || $prefixIcon || filled($prefixLabel)),
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Forms\Components\Concerns;
|
||||
|
||||
use Closure;
|
||||
|
||||
trait CanBeNative
|
||||
{
|
||||
protected bool | Closure $isNative = true;
|
||||
|
||||
public function native(bool | Closure $condition = true): static
|
||||
{
|
||||
$this->isNative = $condition;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isNative(): bool
|
||||
{
|
||||
return (bool) $this->evaluate($this->isNative);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Filament\Forms\Components\Concerns;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
trait CanDisableOptions
|
||||
{
|
||||
@@ -20,11 +21,16 @@ trait CanDisableOptions
|
||||
*/
|
||||
public function getEnabledOptions(): array
|
||||
{
|
||||
return array_filter(
|
||||
$this->getOptions(),
|
||||
fn ($label, $value) => ! $this->isOptionDisabled($value, $label),
|
||||
ARRAY_FILTER_USE_BOTH,
|
||||
);
|
||||
return collect($this->getOptions())
|
||||
->reduce(function (Collection $carry, $label, $value): Collection {
|
||||
if (is_array($label)) {
|
||||
return $carry->merge($label);
|
||||
}
|
||||
|
||||
return $carry->put($value, $label);
|
||||
}, collect())
|
||||
->filter(fn ($label, $value) => ! $this->isOptionDisabled($value, $label))
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,6 +36,8 @@ trait HasState
|
||||
|
||||
protected bool | Closure $isDehydrated = true;
|
||||
|
||||
protected bool | Closure $isDehydratedWhenHidden = false;
|
||||
|
||||
protected ?string $statePath = null;
|
||||
|
||||
protected string $cachedAbsoluteStatePath;
|
||||
@@ -135,6 +137,13 @@ trait HasState
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function dehydratedWhenHidden(bool | Closure $condition = true): static
|
||||
{
|
||||
$this->isDehydratedWhenHidden = $condition;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function formatStateUsing(?Closure $callback): static
|
||||
{
|
||||
$this->afterStateHydrated(fn (Component $component) => $component->state($component->evaluate($callback)));
|
||||
@@ -432,6 +441,20 @@ trait HasState
|
||||
return (bool) $this->evaluate($this->isDehydrated);
|
||||
}
|
||||
|
||||
public function isDehydratedWhenHidden(): bool
|
||||
{
|
||||
return (bool) $this->evaluate($this->isDehydratedWhenHidden);
|
||||
}
|
||||
|
||||
public function isHiddenAndNotDehydrated(): bool
|
||||
{
|
||||
if (! $this->isHidden()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ! $this->isDehydratedWhenHidden();
|
||||
}
|
||||
|
||||
public function getGetCallback(): Get
|
||||
{
|
||||
return new Get($this);
|
||||
|
||||
@@ -12,6 +12,7 @@ use Illuminate\View\ComponentAttributeBag;
|
||||
|
||||
class DateTimePicker extends Field implements Contracts\HasAffixActions
|
||||
{
|
||||
use Concerns\CanBeNative;
|
||||
use Concerns\CanBeReadOnly;
|
||||
use Concerns\HasAffixes;
|
||||
use Concerns\HasDatalistOptions;
|
||||
@@ -36,8 +37,6 @@ class DateTimePicker extends Field implements Contracts\HasAffixActions
|
||||
|
||||
protected string | Closure | null $format = null;
|
||||
|
||||
protected bool | Closure $isNative = true;
|
||||
|
||||
protected bool | Closure $hasDate = true;
|
||||
|
||||
protected bool | Closure $hasSeconds = true;
|
||||
@@ -274,13 +273,6 @@ class DateTimePicker extends Field implements Contracts\HasAffixActions
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function native(bool | Closure $condition = true): static
|
||||
{
|
||||
$this->isNative = $condition;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function date(bool | Closure $condition = true): static
|
||||
{
|
||||
$this->hasDate = $condition;
|
||||
@@ -467,11 +459,6 @@ class DateTimePicker extends Field implements Contracts\HasAffixActions
|
||||
return (bool) $this->evaluate($this->shouldCloseOnDateSelection);
|
||||
}
|
||||
|
||||
public function isNative(): bool
|
||||
{
|
||||
return (bool) $this->evaluate($this->isNative);
|
||||
}
|
||||
|
||||
public function getStep(): int | float | string | null
|
||||
{
|
||||
$step = $this->evaluate($this->step);
|
||||
|
||||
@@ -36,6 +36,8 @@ class FileUpload extends BaseFileUpload
|
||||
|
||||
protected bool | Closure $isAvatar = false;
|
||||
|
||||
protected int | float | Closure | null $itemPanelAspectRatio = null;
|
||||
|
||||
protected string | Closure $loadingIndicatorPosition = 'right';
|
||||
|
||||
protected string | Closure | null $panelAspectRatio = null;
|
||||
@@ -160,6 +162,13 @@ class FileUpload extends BaseFileUpload
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function itemPanelAspectRatio(int | float | Closure | null $ratio): static
|
||||
{
|
||||
$this->itemPanelAspectRatio = $ratio;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function loadingIndicatorPosition(string | Closure | null $position): static
|
||||
{
|
||||
$this->loadingIndicatorPosition = $position;
|
||||
@@ -249,6 +258,20 @@ class FileUpload extends BaseFileUpload
|
||||
return (bool) $this->evaluate($this->imageResizeUpscale);
|
||||
}
|
||||
|
||||
public function getItemPanelAspectRatio(): int | float | null
|
||||
{
|
||||
$itemPanelAspectRatio = $this->evaluate($this->itemPanelAspectRatio);
|
||||
|
||||
if (
|
||||
($this->getPanelLayout() === 'grid') &&
|
||||
(! $itemPanelAspectRatio)
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return $itemPanelAspectRatio;
|
||||
}
|
||||
|
||||
public function getLoadingIndicatorPosition(): string
|
||||
{
|
||||
return $this->evaluate($this->loadingIndicatorPosition);
|
||||
|
||||
@@ -11,6 +11,7 @@ use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
class MorphToSelect extends Component
|
||||
{
|
||||
use Concerns\CanAllowHtml;
|
||||
use Concerns\CanBeNative;
|
||||
use Concerns\CanBePreloaded;
|
||||
use Concerns\CanBeSearchable;
|
||||
use Concerns\HasLoadingMessage;
|
||||
@@ -66,6 +67,7 @@ class MorphToSelect extends Component
|
||||
fn (Type $type): string => $type->getLabel(),
|
||||
$types,
|
||||
))
|
||||
->native($this->isNative())
|
||||
->required($isRequired)
|
||||
->live()
|
||||
->afterStateUpdated(function (Set $set) use ($keyColumn) {
|
||||
@@ -78,8 +80,10 @@ class MorphToSelect extends Component
|
||||
->options($selectedType?->getOptionsUsing)
|
||||
->getSearchResultsUsing($selectedType?->getSearchResultsUsing)
|
||||
->getOptionLabelUsing($selectedType?->getOptionLabelUsing)
|
||||
->required($isRequired)
|
||||
->hidden(! $selectedType)
|
||||
->native($this->isNative())
|
||||
->required(filled($selectedType))
|
||||
->hidden(blank($selectedType))
|
||||
->dehydratedWhenHidden()
|
||||
->searchable($this->isSearchable())
|
||||
->searchDebounce($this->getSearchDebounce())
|
||||
->searchPrompt($this->getSearchPrompt())
|
||||
|
||||
@@ -33,6 +33,7 @@ use function Filament\Support\generate_search_term_expression;
|
||||
class Select extends Field implements Contracts\CanDisableOptions, Contracts\HasAffixActions, Contracts\HasNestedRecursiveValidationRules
|
||||
{
|
||||
use Concerns\CanAllowHtml;
|
||||
use Concerns\CanBeNative;
|
||||
use Concerns\CanBePreloaded;
|
||||
use Concerns\CanBeSearchable;
|
||||
use Concerns\CanDisableOptions;
|
||||
@@ -83,8 +84,6 @@ class Select extends Field implements Contracts\CanDisableOptions, Contracts\Has
|
||||
|
||||
protected bool | Closure $isMultiple = false;
|
||||
|
||||
protected bool | Closure $isNative = true;
|
||||
|
||||
protected ?Closure $getOptionLabelUsing = null;
|
||||
|
||||
protected ?Closure $getOptionLabelsUsing = null;
|
||||
@@ -552,13 +551,6 @@ class Select extends Field implements Contracts\CanDisableOptions, Contracts\Has
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function native(bool | Closure $condition = true): static
|
||||
{
|
||||
$this->isNative = $condition;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function position(string | Closure | null $position): static
|
||||
{
|
||||
$this->position = $position;
|
||||
@@ -694,11 +686,6 @@ class Select extends Field implements Contracts\CanDisableOptions, Contracts\Has
|
||||
return (bool) $this->evaluate($this->isMultiple);
|
||||
}
|
||||
|
||||
public function isNative(): bool
|
||||
{
|
||||
return (bool) $this->evaluate($this->isNative);
|
||||
}
|
||||
|
||||
public function isSearchable(): bool
|
||||
{
|
||||
return $this->evaluate($this->isSearchable) || $this->isMultiple();
|
||||
@@ -971,6 +958,7 @@ class Select extends Field implements Contracts\CanDisableOptions, Contracts\Has
|
||||
|
||||
if (! $relationship instanceof BelongsToMany) {
|
||||
$relationship->associate($state);
|
||||
$record->wasRecentlyCreated && $record->save();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Filament\Forms\Concerns;
|
||||
|
||||
use Filament\Forms\Components;
|
||||
use Filament\Forms\Components\Component;
|
||||
|
||||
trait CanBeValidated
|
||||
{
|
||||
@@ -13,7 +14,11 @@ trait CanBeValidated
|
||||
{
|
||||
$attributes = [];
|
||||
|
||||
foreach ($this->getComponents() as $component) {
|
||||
foreach ($this->getComponents(withHidden: true) as $component) {
|
||||
if ($component->isHiddenAndNotDehydrated()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($component instanceof Components\Contracts\HasValidationRules) {
|
||||
$component->dehydrateValidationAttributes($attributes);
|
||||
}
|
||||
@@ -40,7 +45,11 @@ trait CanBeValidated
|
||||
{
|
||||
$messages = [];
|
||||
|
||||
foreach ($this->getComponents() as $component) {
|
||||
foreach ($this->getComponents(withHidden: true) as $component) {
|
||||
if ($component->isHiddenAndNotDehydrated()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($component instanceof Components\Contracts\HasValidationRules) {
|
||||
$component->dehydrateValidationMessages($messages);
|
||||
}
|
||||
@@ -67,7 +76,11 @@ trait CanBeValidated
|
||||
{
|
||||
$rules = [];
|
||||
|
||||
foreach ($this->getComponents() as $component) {
|
||||
foreach ($this->getComponents(withHidden: true) as $component) {
|
||||
if ($component->isHiddenAndNotDehydrated()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($component instanceof Components\Contracts\HasValidationRules) {
|
||||
$component->dehydrateValidationRules($rules);
|
||||
}
|
||||
@@ -92,7 +105,10 @@ trait CanBeValidated
|
||||
*/
|
||||
public function validate(): array
|
||||
{
|
||||
if (! count($this->getComponents())) {
|
||||
if (! count(array_filter(
|
||||
$this->getComponents(withHidden: true),
|
||||
fn (Component $component): bool => ! $component->isHiddenAndNotDehydrated(),
|
||||
))) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
@@ -70,8 +70,8 @@ trait HasState
|
||||
*/
|
||||
public function dehydrateState(array &$state = [], bool $isDehydrated = true): array
|
||||
{
|
||||
foreach ($this->getComponents() as $component) {
|
||||
if ($component->isHidden()) {
|
||||
foreach ($this->getComponents(withHidden: true) as $component) {
|
||||
if ($component->isHiddenAndNotDehydrated()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -87,8 +87,8 @@ trait HasState
|
||||
*/
|
||||
public function mutateDehydratedState(array &$state = []): array
|
||||
{
|
||||
foreach ($this->getComponents() as $component) {
|
||||
if ($component->isHidden()) {
|
||||
foreach ($this->getComponents(withHidden: true) as $component) {
|
||||
if ($component->isHiddenAndNotDehydrated()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -130,8 +130,8 @@ trait HasState
|
||||
*/
|
||||
public function mutateStateForValidation(array &$state = []): array
|
||||
{
|
||||
foreach ($this->getComponents() as $component) {
|
||||
if ($component->isHidden()) {
|
||||
foreach ($this->getComponents(withHidden: true) as $component) {
|
||||
if ($component->isHiddenAndNotDehydrated()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -132,6 +132,17 @@ TextEntry::make('description')
|
||||
->words(10)
|
||||
```
|
||||
|
||||
## Limiting text to a specific number of lines
|
||||
|
||||
You may want to limit text to a specific number of lines instead of limiting it to a fixed length. Clamping text to a number of lines is useful in responsive interfaces where you want to ensure a consistent experience across all screen sizes. This can be achieved using the `lineClamp()` method:
|
||||
|
||||
```php
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
|
||||
TextEntry::make('description')
|
||||
->lineClamp(2)
|
||||
```
|
||||
|
||||
## Listing multiple values
|
||||
|
||||
By default, if there are multiple values inside your text entry, they will be comma-separated. You may use the `listWithLineBreaks()` method to display them on new lines instead:
|
||||
|
||||
@@ -15,6 +15,24 @@ return [
|
||||
|
||||
],
|
||||
|
||||
'key_value' => [
|
||||
|
||||
'columns' => [
|
||||
|
||||
'key' => [
|
||||
'label' => 'المفتاح',
|
||||
],
|
||||
|
||||
'value' => [
|
||||
'label' => 'القيمة',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'placeholder' => 'لا توجد مدخلات',
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -2,8 +2,37 @@
|
||||
|
||||
return [
|
||||
|
||||
'text_entry' => [
|
||||
'more_list_items' => 'und :count weitere',
|
||||
'entries' => [
|
||||
|
||||
'text' => [
|
||||
|
||||
'actions' => [
|
||||
'collapse_list' => 'Zeige :count weniger',
|
||||
'expand_list' => 'Zeige :count weitere',
|
||||
],
|
||||
|
||||
'more_list_items' => 'und :count mehr',
|
||||
|
||||
],
|
||||
|
||||
'key_value' => [
|
||||
|
||||
'columns' => [
|
||||
|
||||
'key' => [
|
||||
'label' => 'Schlüssel',
|
||||
],
|
||||
|
||||
'value' => [
|
||||
'label' => 'Wert',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'placeholder' => 'Keine Einträge',
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -15,6 +15,24 @@ return [
|
||||
|
||||
],
|
||||
|
||||
'key_value' => [
|
||||
|
||||
'columns' => [
|
||||
|
||||
'key' => [
|
||||
'label' => 'Khóa',
|
||||
],
|
||||
|
||||
'value' => [
|
||||
'label' => 'Giá trị',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'placeholder' => 'Không có mục',
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'entries' => [
|
||||
|
||||
'text' => [
|
||||
|
||||
'actions' => [
|
||||
'collapse_list' => '少顯示 :count 筆',
|
||||
'expand_list' => '多顯示 :count 筆',
|
||||
],
|
||||
|
||||
'more_list_items' => '多 :count 筆',
|
||||
|
||||
],
|
||||
|
||||
'key_value' => [
|
||||
|
||||
'columns' => [
|
||||
|
||||
'key' => [
|
||||
'label' => '索引',
|
||||
],
|
||||
|
||||
'value' => [
|
||||
'label' => '值',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'placeholder' => '無項目',
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
@@ -114,11 +114,12 @@
|
||||
$icon = $getIcon($state);
|
||||
$iconColor = $getIconColor($state);
|
||||
$itemIsCopyable = $isCopyable($state);
|
||||
$lineClamp = $getLineClamp($state);
|
||||
$size = $getSize($state);
|
||||
$weight = $getWeight($state);
|
||||
|
||||
$proseClasses = \Illuminate\Support\Arr::toCssClasses([
|
||||
'prose max-w-none dark:prose-invert [&>*:first-child]:mt-0 [&>*:last-child]:mb-0',
|
||||
'fi-in-text-item-prose prose max-w-none dark:prose-invert [&>*:first-child]:mt-0 [&>*:last-child]:mb-0',
|
||||
'pt-2' => ! $isLabelHidden(),
|
||||
match ($size) {
|
||||
TextEntrySize::ExtraSmall, 'xs' => 'prose-xs',
|
||||
@@ -200,6 +201,7 @@
|
||||
@class([
|
||||
'group-hover/item:underline group-focus-visible/item:underline' => $url,
|
||||
$proseClasses => $isProse || $isMarkdown,
|
||||
'line-clamp-[--line-clamp]' => $lineClamp,
|
||||
match ($size) {
|
||||
TextEntrySize::ExtraSmall, 'xs' => 'text-xs',
|
||||
TextEntrySize::Small, 'sm', null => 'text-sm leading-6',
|
||||
@@ -236,6 +238,7 @@
|
||||
shades: [400, 600],
|
||||
alias: 'infolists::components.text-entry.item.label',
|
||||
) => ! in_array($color, [null, 'gray']),
|
||||
"--line-clamp: {$lineClamp}" => $lineClamp,
|
||||
])
|
||||
>
|
||||
{{ $formattedState }}
|
||||
|
||||
@@ -157,6 +157,7 @@ trait HasState
|
||||
$state = collect($this->getRelationshipResults($record))
|
||||
->filter(fn (Model $record): bool => array_key_exists($relationshipAttribute, $record->attributesToArray()))
|
||||
->pluck($relationshipAttribute)
|
||||
->filter(fn ($state): bool => filled($state))
|
||||
->when($this->isDistinctList(), fn (Collection $state) => $state->unique())
|
||||
->values();
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ use Closure;
|
||||
use Filament\Infolists\Components\Contracts\HasAffixActions;
|
||||
use Filament\Infolists\Components\TextEntry\TextEntrySize;
|
||||
use Filament\Support\Concerns\CanBeCopied;
|
||||
use Filament\Support\Concerns\HasLineClamp;
|
||||
|
||||
class TextEntry extends Entry implements HasAffixActions
|
||||
{
|
||||
@@ -17,6 +18,7 @@ class TextEntry extends Entry implements HasAffixActions
|
||||
use Concerns\HasIcon;
|
||||
use Concerns\HasIconColor;
|
||||
use Concerns\HasWeight;
|
||||
use HasLineClamp;
|
||||
|
||||
/**
|
||||
* @var view-string
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
'modal' => [
|
||||
|
||||
'heading' => '通知',
|
||||
|
||||
'actions' => [
|
||||
|
||||
'clear' => [
|
||||
'label' => '清除',
|
||||
],
|
||||
|
||||
'mark_all_as_read' => [
|
||||
'label' => '標記為已讀',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'empty' => [
|
||||
'heading' => '沒有通知',
|
||||
'description' => '請稍後再查看。',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -43,6 +43,32 @@ Not sure where to start? Review the [Getting Started guide](getting-started) to
|
||||
## Using other Filament packages
|
||||
The Filament Panel Builder pre-installs the [Form Builder](/docs/forms), [Table Builder](/docs/tables), [Notifications](/docs/notifications), [Actions](/docs/actions), [Infolists](/docs/infolists), and [Widgets](/docs/widgets) packages. No other installation steps are required to use these packages within a panel.
|
||||
|
||||
## Improving Filament panel performance
|
||||
|
||||
### Caching Blade Icons
|
||||
|
||||
You may wish to consider running `php artisan icons:cache` locally, and also in your deployment script. This is because Filament uses the [Blade Icons](https://blade-ui-kit.com/blade-icons) package, which can be much more performant when cached.
|
||||
|
||||
### Caching Filament components
|
||||
|
||||
You may also wish to consider running `php artisan filament:cache-components` in your deployment script, especially if you have large numbers of components (resources, pages, widgets, relation managers, custom Livewire components, etc.). This will create cache files in the `bootstrap/cache/filament` directory of your application, which contain indexes for each type of component. This can significantly improve the performance of Filament in some apps, as it reduces the number of files that need to be scanned and auto-discovered for components.
|
||||
|
||||
However, if you are actively developing your app locally, you should avoid using this command, as it will prevent any new components from being discovered until the cache is cleared or rebuilt.
|
||||
|
||||
You can clear the cache at any time without rebuilding it by running `php artisan filament:clear-cached-components`.
|
||||
|
||||
### Enabling OPcache on your server
|
||||
|
||||
From the [Laravel Forge documentation](https://forge.laravel.com/docs/servers/php.html#opcache):
|
||||
|
||||
> Optimizing the PHP OPcache for production will configure OPcache to store your compiled PHP code in memory to greatly improve performance.
|
||||
|
||||
Please use a search engine to find the relevant OPcache setup instructions for your environment.
|
||||
|
||||
### Optimizing your Laravel app
|
||||
|
||||
You should also consider optimizing your Laravel app for production by running `php artisan optimize` in your deployment script. This will cache the configuration files and routes.
|
||||
|
||||
## Deploying to production
|
||||
|
||||
### Allowing users to access a panel
|
||||
@@ -73,10 +99,6 @@ class User extends Authenticatable implements FilamentUser
|
||||
|
||||
Learn more about [users](users).
|
||||
|
||||
### Caching Blade Icons
|
||||
|
||||
You may wish to consider using `php artisan icons:cache` in your deployment process, as Filament uses the [Blade Icons](https://blade-ui-kit.com/blade-icons) package, which can be much more performant when cached. This is very hardware-dependant though, and many servers do not require this command for Filament to run smoothly.
|
||||
|
||||
## Publishing configuration
|
||||
|
||||
You can publish the Filament package configuration (if needed) using the following command:
|
||||
|
||||
@@ -229,6 +229,16 @@ public function panel(Panel $panel): Panel
|
||||
}
|
||||
```
|
||||
|
||||
### Navigation groups in a collapsible sidebar on desktop
|
||||
|
||||
> This section only applies to `sidebarCollapsibleOnDesktop()`, not `sidebarFullyCollapsibleOnDesktop()`, since the fully collapsible UI just hides the entire sidebar instead of changing its design.
|
||||
|
||||
When using a collapsible sidebar on desktop, you will also often be using [navigation groups](#grouping-navigation-items). By default, the labels of each navigation group will be hidden when the sidebar is collapsed, since there is no space to display them. Even if the navigation group itself is [collapsible](#making-navigation-groups-not-collapsible), all items will still be visible in the collapsed sidebar, since there is no group label to click on to expand the group.
|
||||
|
||||
These issues can be solved, to achieve a very minimal sidebar design, by [passing an `icon()`](#customizing-navigation-groups) to the navigation group objects. When an icon is defined, the icon will be displayed in the collapsed sidebar instead of the items at all times. When the icon is clicked, a dropdown will open to the side of the icon, revealing the items in the group.
|
||||
|
||||
When passing an icon to a navigation group, even if the items also have icons, the expanded sidebar UI will not show the item icons. This is to keep the navigation hierarchy clear, and the design minimal. However, the icons for the items will be shown in the collapsed sidebar's dropdowns though, since the hierarchy is already clear from the fact that the dropdown is open.
|
||||
|
||||
## Registering custom navigation items
|
||||
|
||||
To register new navigation items, you can use the [configuration](configuration):
|
||||
|
||||
@@ -6,6 +6,10 @@ return [
|
||||
|
||||
'actions' => [
|
||||
|
||||
'billing' => [
|
||||
'label' => 'Abonnement verwalten',
|
||||
],
|
||||
|
||||
'logout' => [
|
||||
'label' => 'Abmelden',
|
||||
],
|
||||
|
||||
@@ -52,7 +52,8 @@ return [
|
||||
'notifications' => [
|
||||
|
||||
'throttled' => [
|
||||
'title' => 'Zu viele Loginversuche. Versuchen Sie es bitte in :seconds Sekunden nochmal.',
|
||||
'title' => 'Zu viele Loginversuche.',
|
||||
'body' => ' Bitte in :seconds Sekunden nochmal versuchen.',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -34,7 +34,9 @@ return [
|
||||
'notifications' => [
|
||||
|
||||
'throttled' => [
|
||||
'title' => 'Zu viele Versuche. Versuchen Sie es bitte in :seconds Sekunden nochmal.',
|
||||
'title' => 'Zu viele Versuche.',
|
||||
'body' => 'Bitte in :seconds Sekunden nochmal versuchen.',
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -47,7 +47,8 @@ return [
|
||||
'notifications' => [
|
||||
|
||||
'throttled' => [
|
||||
'title' => 'Zu viele Anmeldeversuche. Versuchen Sie es bitte in :seconds Sekunden nochmal.',
|
||||
'title' => 'Zu viele Anmeldeversuche.',
|
||||
'body' => 'Bitte in :seconds Sekunden nochmal versuchen.',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -4,4 +4,29 @@ return [
|
||||
|
||||
'title' => 'Dashboard',
|
||||
|
||||
'actions' => [
|
||||
|
||||
'filter' => [
|
||||
|
||||
'label' => 'Filter',
|
||||
|
||||
'modal' => [
|
||||
|
||||
'heading' => 'Filter',
|
||||
|
||||
'actions' => [
|
||||
|
||||
'apply' => [
|
||||
|
||||
'label' => 'Übernehmen',
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
];
|
||||
|
||||
@@ -43,7 +43,7 @@ return [
|
||||
'actions' => [
|
||||
|
||||
'cancel' => [
|
||||
'label' => 'indietro',
|
||||
'label' => 'Indietro',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
@props([
|
||||
'actions' => [],
|
||||
'breadcrumbs' => [],
|
||||
'heading',
|
||||
'subheading' => null,
|
||||
])
|
||||
|
||||
@php
|
||||
// These are passed through in the bag otherwise Laravel converts View objects to strings prematurely.
|
||||
$heading = $attributes->get('heading');
|
||||
$subheading = $attributes->get('subheading');
|
||||
$attributes = $attributes->except(['heading', 'subheading']);
|
||||
@endphp
|
||||
|
||||
<header
|
||||
{{ $attributes->class(['fi-header flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between']) }}
|
||||
>
|
||||
|
||||
@@ -5,20 +5,10 @@
|
||||
@endphp
|
||||
|
||||
<x-filament-panels::layout.base :livewire="$livewire">
|
||||
<div class="fi-layout flex min-h-screen w-full overflow-x-clip">
|
||||
@if (filament()->hasNavigation())
|
||||
<div
|
||||
x-cloak
|
||||
x-data="{}"
|
||||
x-on:click="$store.sidebar.close()"
|
||||
x-show="$store.sidebar.isOpen"
|
||||
x-transition.opacity.300ms
|
||||
class="fi-sidebar-close-overlay fixed inset-0 z-30 bg-gray-950/50 transition duration-500 dark:bg-gray-950/75 lg:hidden"
|
||||
></div>
|
||||
|
||||
<x-filament-panels::sidebar :navigation="$navigation" />
|
||||
@endif
|
||||
|
||||
{{-- The sidebar is after the page content in the markup to fix issues with page content overlapping dropdown content from the sidebar. --}}
|
||||
<div
|
||||
class="fi-layout flex min-h-screen w-full flex-row-reverse overflow-x-clip"
|
||||
>
|
||||
<div
|
||||
@if (filament()->isSidebarCollapsibleOnDesktop())
|
||||
x-data="{}"
|
||||
@@ -89,5 +79,18 @@
|
||||
|
||||
{{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::FOOTER, scopes: $livewire->getRenderHookScopes()) }}
|
||||
</div>
|
||||
|
||||
@if (filament()->hasNavigation())
|
||||
<div
|
||||
x-cloak
|
||||
x-data="{}"
|
||||
x-on:click="$store.sidebar.close()"
|
||||
x-show="$store.sidebar.isOpen"
|
||||
x-transition.opacity.300ms
|
||||
class="fi-sidebar-close-overlay fixed inset-0 z-30 bg-gray-950/50 transition duration-500 dark:bg-gray-950/75 lg:hidden"
|
||||
></div>
|
||||
|
||||
<x-filament-panels::sidebar :navigation="$navigation" />
|
||||
@endif
|
||||
</div>
|
||||
</x-filament-panels::layout.base>
|
||||
|
||||
@@ -32,8 +32,13 @@
|
||||
<x-filament-panels::header
|
||||
:actions="$this->getCachedHeaderActions()"
|
||||
:breadcrumbs="filament()->hasBreadcrumbs() ? $this->getBreadcrumbs() : []"
|
||||
:heading="$heading"
|
||||
:subheading="$this->getSubheading()"
|
||||
{{-- These are passed through in the bag otherwise Laravel converts View objects to strings prematurely. --}}
|
||||
:attributes="
|
||||
new \Illuminate\View\ComponentAttributeBag([
|
||||
'heading' => $heading,
|
||||
'subheading' => $this->getSubheading(),
|
||||
])
|
||||
"
|
||||
/>
|
||||
@endif
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
>
|
||||
@foreach ($navigation as $navigationGroup)
|
||||
<x-filament-panels::sidebar.group
|
||||
:active="$navigationGroup->isActive()"
|
||||
:collapsible="$navigationGroup->isCollapsible()"
|
||||
:icon="$navigationGroup->getIcon()"
|
||||
:items="$navigationGroup->getItems()"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@props([
|
||||
'active' => false,
|
||||
'collapsible' => true,
|
||||
'icon' => null,
|
||||
'items' => [],
|
||||
@@ -7,6 +8,11 @@
|
||||
'subNavigation' => false,
|
||||
])
|
||||
|
||||
@php
|
||||
$sidebarCollapsible = $sidebarCollapsible && filament()->isSidebarCollapsibleOnDesktop();
|
||||
$hasDropdown = filled($label) && filled($icon) && $sidebarCollapsible;
|
||||
@endphp
|
||||
|
||||
<li
|
||||
x-data="{ label: @js($subNavigation ? "sub_navigation_{$label}" : $label) }"
|
||||
data-group-label="{{ $label }}"
|
||||
@@ -17,7 +23,7 @@
|
||||
@if ($collapsible)
|
||||
x-on:click="$store.sidebar.toggleCollapsedGroup(label)"
|
||||
@endif
|
||||
@if (filament()->isSidebarCollapsibleOnDesktop())
|
||||
@if ($sidebarCollapsible)
|
||||
x-show="$store.sidebar.isOpen"
|
||||
x-transition:enter="delay-100 lg:transition"
|
||||
x-transition:enter-start="opacity-0"
|
||||
@@ -56,17 +62,118 @@
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($hasDropdown)
|
||||
<x-filament::dropdown
|
||||
:placement="(__('filament-panels::layout.direction') === 'rtl') ? 'left-start' : 'right-start'"
|
||||
teleport
|
||||
x-show="! $store.sidebar.isOpen"
|
||||
>
|
||||
<x-slot name="trigger">
|
||||
<button
|
||||
x-data="{ tooltip: false }"
|
||||
x-effect="
|
||||
tooltip = $store.sidebar.isOpen
|
||||
? false
|
||||
: {
|
||||
content: @js($label),
|
||||
placement: document.dir === 'rtl' ? 'left' : 'right',
|
||||
theme: $store.theme,
|
||||
}
|
||||
"
|
||||
x-tooltip.html="tooltip"
|
||||
class="relative flex flex-1 items-center justify-center gap-x-3 rounded-lg px-2 py-2 outline-none transition duration-75 hover:bg-gray-100 focus-visible:bg-gray-100 dark:hover:bg-white/5 dark:focus-visible:bg-white/5"
|
||||
>
|
||||
<x-filament::icon
|
||||
:icon="$icon"
|
||||
@class([
|
||||
'h-6 w-6',
|
||||
'text-gray-400 dark:text-gray-500' => ! $active,
|
||||
'text-primary-600 dark:text-primary-400' => $active,
|
||||
])
|
||||
/>
|
||||
</button>
|
||||
</x-slot>
|
||||
|
||||
@php
|
||||
$lists = [];
|
||||
|
||||
foreach ($items as $item) {
|
||||
if ($childItems = $item->getChildItems()) {
|
||||
$lists[] = [
|
||||
$item,
|
||||
...$childItems,
|
||||
];
|
||||
$lists[] = [];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($lists)) {
|
||||
$lists[] = [$item];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$lists[count($lists) - 1][] = $item;
|
||||
}
|
||||
|
||||
if (empty($lists[count($lists) - 1])) {
|
||||
array_pop($lists);
|
||||
}
|
||||
@endphp
|
||||
|
||||
@if (filled($label))
|
||||
<x-filament::dropdown.header>
|
||||
{{ $label }}
|
||||
</x-filament::dropdown.header>
|
||||
@endif
|
||||
|
||||
@foreach ($lists as $list)
|
||||
<x-filament::dropdown.list>
|
||||
@foreach ($list as $item)
|
||||
@php
|
||||
$itemIsActive = $item->isActive();
|
||||
@endphp
|
||||
|
||||
<x-filament::dropdown.list.item
|
||||
:badge="$item->getBadge()"
|
||||
:badge-color="$item->getBadgeColor()"
|
||||
:badge-tooltip="$item->getBadgeTooltip()"
|
||||
:color="$itemIsActive ? 'primary' : 'gray'"
|
||||
:href="$item->getUrl()"
|
||||
:icon="$itemIsActive ? ($item->getActiveIcon() ?? $item->getIcon()) : $item->getIcon()"
|
||||
tag="a"
|
||||
:target="$item->shouldOpenUrlInNewTab() ? '_blank' : null"
|
||||
>
|
||||
{{ $item->getLabel() }}
|
||||
</x-filament::dropdown.list.item>
|
||||
@endforeach
|
||||
</x-filament::dropdown.list>
|
||||
@endforeach
|
||||
</x-filament::dropdown>
|
||||
@endif
|
||||
|
||||
<ul
|
||||
x-show="! ($store.sidebar.groupIsCollapsed(label) && ($store.sidebar.isOpen || @js(! filament()->isSidebarCollapsibleOnDesktop())))"
|
||||
@if (filament()->isSidebarCollapsibleOnDesktop())
|
||||
@if (filled($label))
|
||||
@if ($sidebarCollapsible)
|
||||
x-show="$store.sidebar.isOpen ? ! $store.sidebar.groupIsCollapsed(label) : ! @js($hasDropdown)"
|
||||
@else
|
||||
x-show="! $store.sidebar.groupIsCollapsed(label)"
|
||||
@endif
|
||||
x-collapse.duration.200ms
|
||||
@endif
|
||||
@if ($sidebarCollapsible)
|
||||
x-transition:enter="delay-100 lg:transition"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
@endif
|
||||
x-collapse.duration.200ms
|
||||
class="fi-sidebar-group-items flex flex-col gap-y-1"
|
||||
>
|
||||
@foreach ($items as $item)
|
||||
@php
|
||||
$itemIcon = $item->getIcon();
|
||||
@endphp
|
||||
|
||||
<x-filament-panels::sidebar.item
|
||||
:active="$item->isActive()"
|
||||
:active-child-items="$item->isChildItemsActive()"
|
||||
@@ -77,7 +184,7 @@
|
||||
:child-items="$item->getChildItems()"
|
||||
:first="$loop->first"
|
||||
:grouped="filled($label)"
|
||||
:icon="$item->getIcon()"
|
||||
:icon="$icon ? (($hasDropdown || blank($itemIcon)) ? null : throw new \Exception('Navigation group [' . $label . '] has an icon but one or more of its items also have icons. Either the group or its items can have icons, but not both. This is to ensure a proper user experience.')) : $itemIcon"
|
||||
:last="$loop->last"
|
||||
:should-open-url-in-new-tab="$item->shouldOpenUrlInNewTab()"
|
||||
:sidebar-collapsible="$sidebarCollapsible"
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
<ul class="fi-sidebar-nav-groups -mx-2 flex flex-col gap-y-7">
|
||||
@foreach ($navigation as $group)
|
||||
<x-filament-panels::sidebar.group
|
||||
:active="$group->isActive()"
|
||||
:collapsible="$group->isCollapsible()"
|
||||
:icon="$group->getIcon()"
|
||||
:items="$group->getItems()"
|
||||
|
||||
@@ -51,10 +51,10 @@
|
||||
'bg-gray-100 dark:bg-white/5' => $active,
|
||||
])
|
||||
>
|
||||
@if (filled($icon) && ((! $subGrouped) || filament()->isSidebarCollapsibleOnDesktop()))
|
||||
@if (filled($icon) && ((! $subGrouped) || $sidebarCollapsible))
|
||||
<x-filament::icon
|
||||
:icon="($active && $activeIcon) ? $activeIcon : $icon"
|
||||
:x-show="($subGrouped && filament()->isSidebarCollapsibleOnDesktop()) ? '! $store.sidebar.isOpen' : false"
|
||||
:x-show="($subGrouped && $sidebarCollapsible) ? '! $store.sidebar.isOpen' : false"
|
||||
@class([
|
||||
'fi-sidebar-item-icon h-6 w-6',
|
||||
'text-gray-400 dark:text-gray-500' => ! $active,
|
||||
@@ -65,7 +65,7 @@
|
||||
|
||||
@if ((blank($icon) && $grouped) || $subGrouped)
|
||||
<div
|
||||
@if (filled($icon) && $subGrouped && filament()->isSidebarCollapsibleOnDesktop())
|
||||
@if (filled($icon) && $subGrouped && $sidebarCollapsible)
|
||||
x-show="$store.sidebar.isOpen"
|
||||
@endif
|
||||
class="fi-sidebar-item-grouped-border relative flex h-6 w-6 items-center justify-center"
|
||||
|
||||
@@ -79,27 +79,56 @@
|
||||
</x-filament-panels::topbar.item>
|
||||
</x-slot>
|
||||
|
||||
<x-filament::dropdown.list>
|
||||
@foreach ($group->getItems() as $item)
|
||||
@php
|
||||
$icon = $item->getIcon();
|
||||
$isActive = $item->isActive();
|
||||
@endphp
|
||||
@php
|
||||
$lists = [];
|
||||
|
||||
<x-filament::dropdown.list.item
|
||||
:badge="$item->getBadge()"
|
||||
:badge-color="$item->getBadgeColor()"
|
||||
:badge-tooltip="$item->getBadgeTooltip()"
|
||||
:color="$isActive ? 'primary' : 'gray'"
|
||||
:href="$item->getUrl()"
|
||||
:icon="$isActive ? ($item->getActiveIcon() ?? $icon) : $icon"
|
||||
tag="a"
|
||||
:target="$item->shouldOpenUrlInNewTab() ? '_blank' : null"
|
||||
>
|
||||
{{ $item->getLabel() }}
|
||||
</x-filament::dropdown.list.item>
|
||||
@endforeach
|
||||
</x-filament::dropdown.list>
|
||||
foreach ($group->getItems() as $item) {
|
||||
if ($childItems = $item->getChildItems()) {
|
||||
$lists[] = [
|
||||
$item,
|
||||
...$childItems,
|
||||
];
|
||||
$lists[] = [];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($lists)) {
|
||||
$lists[] = [$item];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$lists[count($lists) - 1][] = $item;
|
||||
}
|
||||
|
||||
if (empty($lists[count($lists) - 1])) {
|
||||
array_pop($lists);
|
||||
}
|
||||
@endphp
|
||||
|
||||
@foreach ($lists as $list)
|
||||
<x-filament::dropdown.list>
|
||||
@foreach ($list as $item)
|
||||
@php
|
||||
$itemIsActive = $item->isActive();
|
||||
@endphp
|
||||
|
||||
<x-filament::dropdown.list.item
|
||||
:badge="$item->getBadge()"
|
||||
:badge-color="$item->getBadgeColor()"
|
||||
:badge-tooltip="$item->getBadgeTooltip()"
|
||||
:color="$itemIsActive ? 'primary' : 'gray'"
|
||||
:href="$item->getUrl()"
|
||||
:icon="$itemIsActive ? ($item->getActiveIcon() ?? $item->getIcon()) : $item->getIcon()"
|
||||
tag="a"
|
||||
:target="$item->shouldOpenUrlInNewTab() ? '_blank' : null"
|
||||
>
|
||||
{{ $item->getLabel() }}
|
||||
</x-filament::dropdown.list.item>
|
||||
@endforeach
|
||||
</x-filament::dropdown.list>
|
||||
@endforeach
|
||||
</x-filament::dropdown>
|
||||
@else
|
||||
@foreach ($group->getItems() as $item)
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Commands;
|
||||
|
||||
use Filament\Facades\Filament;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class CacheComponentsCommand extends Command
|
||||
{
|
||||
protected $description = 'Cache all components';
|
||||
|
||||
protected $signature = 'filament:cache-components';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$this->info('Caching registered components...');
|
||||
|
||||
foreach (Filament::getPanels() as $panel) {
|
||||
$panel->cacheComponents();
|
||||
}
|
||||
|
||||
$this->info('All done!');
|
||||
|
||||
return static::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Commands;
|
||||
|
||||
use Filament\Facades\Filament;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ClearCachedComponentsCommand extends Command
|
||||
{
|
||||
protected $description = 'Cache all components';
|
||||
|
||||
protected $signature = 'filament:clear-cached-components';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$this->info('Clearing cached components...');
|
||||
|
||||
foreach (Filament::getPanels() as $panel) {
|
||||
$panel->clearCachedComponents();
|
||||
}
|
||||
|
||||
$this->info('All done!');
|
||||
|
||||
return static::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ use Filament\Navigation\MenuItem;
|
||||
use Filament\Navigation\NavigationGroup;
|
||||
use Filament\Navigation\NavigationItem;
|
||||
use Filament\Panel;
|
||||
use Filament\PanelRegistry;
|
||||
use Filament\Support\Assets\Theme;
|
||||
use Filament\Support\Enums\MaxWidth;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
@@ -114,7 +115,6 @@ use Illuminate\Support\Facades\Facade;
|
||||
* @method static bool isServing()
|
||||
* @method static bool isSidebarCollapsibleOnDesktop()
|
||||
* @method static bool isSidebarFullyCollapsibleOnDesktop()
|
||||
* @method static void mountNavigation()
|
||||
* @method static void serving(Closure $callback)
|
||||
* @method static void setCurrentPanel(Panel | null $panel = null)
|
||||
* @method static void setServingStatus(bool $condition = true)
|
||||
@@ -132,8 +132,8 @@ class Filament extends Facade
|
||||
public static function registerPanel(Panel | Closure $panel): void
|
||||
{
|
||||
static::getFacadeApplication()->resolving(
|
||||
static::getFacadeAccessor(),
|
||||
fn (FilamentManager $filamentManager) => $filamentManager->registerPanel(value($panel)),
|
||||
PanelRegistry::class,
|
||||
fn (PanelRegistry $registry) => $registry->register(value($panel)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,11 +34,6 @@ use Illuminate\Support\Facades\Event;
|
||||
|
||||
class FilamentManager
|
||||
{
|
||||
/**
|
||||
* @var array<string, Panel>
|
||||
*/
|
||||
protected array $panels = [];
|
||||
|
||||
protected ?Panel $currentPanel = null;
|
||||
|
||||
protected bool $isServing = false;
|
||||
@@ -47,6 +42,13 @@ class FilamentManager
|
||||
|
||||
protected ?Model $tenant = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// Resolve the panel registry to set the current panel
|
||||
// as the default, which uses a `resolving()` callback.
|
||||
app()->resolved(PanelRegistry::class) || app(PanelRegistry::class);
|
||||
}
|
||||
|
||||
public function auth(): Guard
|
||||
{
|
||||
return $this->getCurrentPanel()->auth();
|
||||
@@ -103,7 +105,7 @@ class FilamentManager
|
||||
|
||||
public function getCurrentPanel(): ?Panel
|
||||
{
|
||||
return $this->currentPanel ?? null;
|
||||
return $this->currentPanel;
|
||||
}
|
||||
|
||||
public function getDarkModeBrandLogo(): string | Htmlable | null
|
||||
@@ -126,11 +128,7 @@ class FilamentManager
|
||||
*/
|
||||
public function getDefaultPanel(): Panel
|
||||
{
|
||||
return Arr::first(
|
||||
$this->panels,
|
||||
fn (Panel $panel): bool => $panel->isDefault(),
|
||||
fn () => throw NoDefaultPanelSetException::make(),
|
||||
);
|
||||
return app(PanelRegistry::class)->getDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -276,7 +274,7 @@ class FilamentManager
|
||||
|
||||
public function getPanel(?string $id = null): Panel
|
||||
{
|
||||
return $this->panels[$id] ?? $this->getDefaultPanel();
|
||||
return app(PanelRegistry::class)->get($id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -284,7 +282,7 @@ class FilamentManager
|
||||
*/
|
||||
public function getPanels(): array
|
||||
{
|
||||
return $this->panels;
|
||||
return app(PanelRegistry::class)->all();
|
||||
}
|
||||
|
||||
public function getPlugin(string $id): Plugin
|
||||
@@ -655,20 +653,9 @@ class FilamentManager
|
||||
return $this->getCurrentPanel()->isSidebarFullyCollapsibleOnDesktop();
|
||||
}
|
||||
|
||||
public function mountNavigation(): void
|
||||
{
|
||||
$this->getCurrentPanel()->mountNavigation();
|
||||
}
|
||||
|
||||
public function registerPanel(Panel $panel): void
|
||||
{
|
||||
$this->panels[$panel->getId()] = $panel;
|
||||
|
||||
$panel->register();
|
||||
|
||||
if ($panel->isDefault()) {
|
||||
$this->setCurrentPanel($panel);
|
||||
}
|
||||
app(PanelRegistry::class)->register($panel);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,6 +18,7 @@ use Filament\Http\Responses\Auth\LoginResponse;
|
||||
use Filament\Http\Responses\Auth\LogoutResponse;
|
||||
use Filament\Http\Responses\Auth\PasswordResetResponse;
|
||||
use Filament\Http\Responses\Auth\RegistrationResponse;
|
||||
use Filament\Navigation\NavigationManager;
|
||||
use Filament\Support\Assets\Js;
|
||||
use Filament\Support\Assets\Theme;
|
||||
use Filament\Support\Facades\FilamentAsset;
|
||||
@@ -47,6 +48,14 @@ class FilamentServiceProvider extends PackageServiceProvider
|
||||
return new FilamentManager();
|
||||
});
|
||||
|
||||
$this->app->singleton(PanelRegistry::class, function (): PanelRegistry {
|
||||
return new PanelRegistry();
|
||||
});
|
||||
|
||||
$this->app->scoped(NavigationManager::class, function (): NavigationManager {
|
||||
return new NavigationManager();
|
||||
});
|
||||
|
||||
$this->app->bind(EmailVerificationResponseContract::class, EmailVerificationResponse::class);
|
||||
$this->app->bind(LoginResponseContract::class, LoginResponse::class);
|
||||
$this->app->bind(LogoutResponseContract::class, LogoutResponse::class);
|
||||
@@ -96,6 +105,8 @@ class FilamentServiceProvider extends PackageServiceProvider
|
||||
protected function getCommands(): array
|
||||
{
|
||||
$commands = [
|
||||
Commands\CacheComponentsCommand::class,
|
||||
Commands\ClearCachedComponentsCommand::class,
|
||||
Commands\MakeClusterCommand::class,
|
||||
Commands\MakePageCommand::class,
|
||||
Commands\MakePanelCommand::class,
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace Filament\Navigation;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Filament\Support\Components\Component;
|
||||
use Filament\Support\Concerns\HasExtraSidebarAttributes;
|
||||
use Filament\Support\Concerns\HasExtraTopbarAttributes;
|
||||
@@ -82,13 +81,7 @@ class NavigationGroup extends Component
|
||||
|
||||
public function getIcon(): ?string
|
||||
{
|
||||
$icon = $this->evaluate($this->icon);
|
||||
|
||||
if ($this->hasItemIcons() && filled($icon)) {
|
||||
throw new Exception("Navigation group [{$this->getLabel()}] has an icon but one or more of its items also have icons. Either the group or its items can have icons, but not both. This is to ensure a proper user experience.");
|
||||
}
|
||||
|
||||
return $icon;
|
||||
return $this->evaluate($this->icon);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,30 +119,4 @@ class NavigationGroup extends Component
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function hasItemIcons(): bool
|
||||
{
|
||||
$hasIconCount = 0;
|
||||
$hasNoIconCount = 0;
|
||||
|
||||
foreach ($this->getItems() as $item) {
|
||||
if (! $item instanceof NavigationItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (blank($item->getIcon())) {
|
||||
$hasNoIconCount++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$hasIconCount++;
|
||||
}
|
||||
|
||||
if (($hasIconCount > 0) && ($hasNoIconCount > 0) && filled($label = $this->getLabel())) {
|
||||
throw new Exception("Navigation group [{$label}] has items with and without icons. All items must have icons or none of them can have icons. This is to ensure a proper user experience.");
|
||||
}
|
||||
|
||||
return $hasIconCount > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Navigation;
|
||||
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Panel;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class NavigationManager
|
||||
{
|
||||
protected Panel $panel;
|
||||
|
||||
protected bool $isNavigationMounted = false;
|
||||
|
||||
/**
|
||||
* @var array<string | int, NavigationGroup | string>
|
||||
*/
|
||||
protected array $navigationGroups = [];
|
||||
|
||||
/**
|
||||
* @var array<NavigationItem>
|
||||
*/
|
||||
protected array $navigationItems = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->panel = Filament::getCurrentPanel();
|
||||
|
||||
$this->navigationGroups = array_map(
|
||||
fn (NavigationGroup | string $group): NavigationGroup | string => $group instanceof NavigationGroup ? (clone $group) : $group,
|
||||
$this->panel->getNavigationGroups(),
|
||||
);
|
||||
$this->navigationItems = array_map(
|
||||
fn (NavigationItem $item): NavigationItem => clone $item,
|
||||
$this->panel->getNavigationItems(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<NavigationGroup>
|
||||
*/
|
||||
public function get(): array
|
||||
{
|
||||
if ($this->panel->hasNavigationBuilder()) {
|
||||
return $this->panel->buildNavigation();
|
||||
}
|
||||
|
||||
if (! $this->isNavigationMounted) {
|
||||
$this->mountNavigation();
|
||||
}
|
||||
|
||||
$groups = collect($this->getNavigationGroups());
|
||||
|
||||
return collect($this->getNavigationItems())
|
||||
->filter(fn (NavigationItem $item): bool => $item->isVisible())
|
||||
->sortBy(fn (NavigationItem $item): int => $item->getSort())
|
||||
->groupBy(fn (NavigationItem $item): ?string => $item->getGroup())
|
||||
->map(function (Collection $items, ?string $groupIndex) use ($groups): NavigationGroup {
|
||||
$parentItems = $items->groupBy(fn (NavigationItem $item): ?string => $item->getParentItem());
|
||||
|
||||
$items = $parentItems->get('')
|
||||
->keyBy(fn (NavigationItem $item): string => $item->getLabel());
|
||||
|
||||
$parentItems->except([''])->each(function (Collection $parentItemItems, string $parentItemLabel) use ($items) {
|
||||
if (! $items->has($parentItemLabel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$items->get($parentItemLabel)->childItems($parentItemItems);
|
||||
});
|
||||
|
||||
if (blank($groupIndex)) {
|
||||
return NavigationGroup::make()->items($items);
|
||||
}
|
||||
|
||||
$registeredGroup = $groups
|
||||
->first(function (NavigationGroup | string $registeredGroup, string | int $registeredGroupIndex) use ($groupIndex) {
|
||||
if ($registeredGroupIndex === $groupIndex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($registeredGroup === $groupIndex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (! $registeredGroup instanceof NavigationGroup) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $registeredGroup->getLabel() === $groupIndex;
|
||||
});
|
||||
|
||||
if ($registeredGroup instanceof NavigationGroup) {
|
||||
return $registeredGroup->items($items);
|
||||
}
|
||||
|
||||
return NavigationGroup::make($registeredGroup ?? $groupIndex)
|
||||
->items($items);
|
||||
})
|
||||
->sortBy(function (NavigationGroup $group, ?string $groupIndex): int {
|
||||
if (blank($group->getLabel())) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
$registeredGroups = $this->getNavigationGroups();
|
||||
|
||||
$groupsToSearch = $registeredGroups;
|
||||
|
||||
if (Arr::first($registeredGroups) instanceof NavigationGroup) {
|
||||
$groupsToSearch = [
|
||||
...array_keys($registeredGroups),
|
||||
...array_map(fn (NavigationGroup $registeredGroup): string => $registeredGroup->getLabel(), array_values($registeredGroups)),
|
||||
];
|
||||
}
|
||||
|
||||
$sort = array_search(
|
||||
$groupIndex,
|
||||
$groupsToSearch,
|
||||
);
|
||||
|
||||
if ($sort === false) {
|
||||
return count($registeredGroups);
|
||||
}
|
||||
|
||||
return $sort;
|
||||
})
|
||||
->all();
|
||||
}
|
||||
|
||||
public function mountNavigation(): void
|
||||
{
|
||||
foreach ($this->panel->getPages() as $page) {
|
||||
$page::registerNavigationItems();
|
||||
}
|
||||
|
||||
foreach ($this->panel->getResources() as $resource) {
|
||||
$resource::registerNavigationItems();
|
||||
}
|
||||
|
||||
$this->isNavigationMounted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string | int, NavigationGroup | string> $groups
|
||||
*/
|
||||
public function navigationGroups(array $groups): static
|
||||
{
|
||||
$this->navigationGroups = [
|
||||
...$this->navigationGroups,
|
||||
...$groups,
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<NavigationItem> $items
|
||||
*/
|
||||
public function navigationItems(array $items): static
|
||||
{
|
||||
$this->navigationItems = [
|
||||
...$this->navigationItems,
|
||||
...$items,
|
||||
];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string | int, NavigationGroup | string>
|
||||
*/
|
||||
public function getNavigationGroups(): array
|
||||
{
|
||||
return $this->navigationGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<NavigationItem>
|
||||
*/
|
||||
public function getNavigationItems(): array
|
||||
{
|
||||
return $this->navigationItems;
|
||||
}
|
||||
}
|
||||
@@ -115,6 +115,13 @@ class EditProfile extends Page
|
||||
static::routes($panel);
|
||||
}
|
||||
|
||||
public static function getRouteName(?string $panel = null): string
|
||||
{
|
||||
$panel = $panel ? Filament::getPanel($panel) : Filament::getCurrentPanel();
|
||||
|
||||
return $panel->generateRouteName('auth.' . static::getRelativeRouteName());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @return array<string, mixed>
|
||||
|
||||
@@ -53,6 +53,13 @@ abstract class EditTenantProfile extends Page
|
||||
return 'profile';
|
||||
}
|
||||
|
||||
public static function getRouteName(?string $panel = null): string
|
||||
{
|
||||
$panel = $panel ? Filament::getPanel($panel) : Filament::getCurrentPanel();
|
||||
|
||||
return $panel->generateRouteName('tenant.' . static::getRelativeRouteName());
|
||||
}
|
||||
|
||||
public static function isTenantSubscriptionRequired(Panel $panel): bool
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -46,10 +46,7 @@ class Panel extends Component
|
||||
|
||||
public static function make(): static
|
||||
{
|
||||
$static = app(static::class);
|
||||
$static->configure();
|
||||
|
||||
return $static;
|
||||
return app(static::class);
|
||||
}
|
||||
|
||||
public function default(bool $condition = true): static
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
namespace Filament\Panel\Concerns;
|
||||
|
||||
use Closure;
|
||||
|
||||
trait HasBreadcrumbs
|
||||
{
|
||||
protected bool $hasBreadcrumbs = true;
|
||||
protected bool | Closure $hasBreadcrumbs = true;
|
||||
|
||||
public function breadcrumbs(bool $condition = true): static
|
||||
public function breadcrumbs(bool | Closure $condition = true): static
|
||||
{
|
||||
$this->hasBreadcrumbs = $condition;
|
||||
|
||||
@@ -15,6 +17,6 @@ trait HasBreadcrumbs
|
||||
|
||||
public function hasBreadcrumbs(): bool
|
||||
{
|
||||
return $this->hasBreadcrumbs;
|
||||
return (bool) $this->evaluate($this->hasBreadcrumbs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,11 +97,17 @@ trait HasComponents
|
||||
|
||||
protected bool | Closure $hasReadOnlyRelationManagersOnResourceViewPagesByDefault = true;
|
||||
|
||||
protected ?bool $hasCachedComponents = null;
|
||||
|
||||
/**
|
||||
* @param array<class-string> $pages
|
||||
*/
|
||||
public function pages(array $pages): static
|
||||
{
|
||||
if ($this->hasCachedComponents()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->pages = [
|
||||
...$this->pages,
|
||||
...$pages,
|
||||
@@ -120,6 +126,10 @@ trait HasComponents
|
||||
*/
|
||||
public function resources(array $resources): static
|
||||
{
|
||||
if ($this->hasCachedComponents()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->resources = [
|
||||
...$this->resources,
|
||||
...$resources,
|
||||
@@ -154,6 +164,10 @@ trait HasComponents
|
||||
*/
|
||||
public function widgets(array $widgets): static
|
||||
{
|
||||
if ($this->hasCachedComponents()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->widgets = [
|
||||
...$this->widgets,
|
||||
...$widgets,
|
||||
@@ -181,6 +195,10 @@ trait HasComponents
|
||||
|
||||
public function discoverPages(string $in, string $for): static
|
||||
{
|
||||
if ($this->hasCachedComponents()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->pageDirectories[] = $in;
|
||||
$this->pageNamespaces[] = $for;
|
||||
|
||||
@@ -218,6 +236,10 @@ trait HasComponents
|
||||
|
||||
public function discoverClusters(string $in, string $for): static
|
||||
{
|
||||
if ($this->hasCachedComponents()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->clusterDirectories[] = $in;
|
||||
$this->clusterNamespaces[] = $for;
|
||||
|
||||
@@ -261,6 +283,10 @@ trait HasComponents
|
||||
|
||||
public function discoverResources(string $in, string $for): static
|
||||
{
|
||||
if ($this->hasCachedComponents()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->resourceDirectories[] = $in;
|
||||
$this->resourceNamespaces[] = $for;
|
||||
|
||||
@@ -298,6 +324,10 @@ trait HasComponents
|
||||
|
||||
public function discoverWidgets(string $in, string $for): static
|
||||
{
|
||||
if ($this->hasCachedComponents()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->widgetDirectories[] = $in;
|
||||
$this->widgetNamespaces[] = $for;
|
||||
|
||||
@@ -329,11 +359,15 @@ trait HasComponents
|
||||
|
||||
public function discoverLivewireComponents(string $in, string $for): static
|
||||
{
|
||||
$component = [];
|
||||
if ($this->hasCachedComponents()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$components = [];
|
||||
|
||||
$this->discoverComponents(
|
||||
Component::class,
|
||||
$component,
|
||||
$components,
|
||||
directory: $in,
|
||||
namespace: $for,
|
||||
);
|
||||
@@ -434,6 +468,10 @@ trait HasComponents
|
||||
*/
|
||||
public function livewireComponents(array $components): static
|
||||
{
|
||||
if ($this->hasCachedComponents()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
foreach ($components as $component) {
|
||||
$this->queueLivewireComponentForRegistration($component);
|
||||
}
|
||||
@@ -443,60 +481,60 @@ trait HasComponents
|
||||
|
||||
protected function registerLivewireComponents(): void
|
||||
{
|
||||
$this->queueLivewireComponentForRegistration(DatabaseNotifications::class);
|
||||
$this->queueLivewireComponentForRegistration(EditProfile::class);
|
||||
$this->queueLivewireComponentForRegistration(GlobalSearch::class);
|
||||
$this->queueLivewireComponentForRegistration(Notifications::class);
|
||||
if (! $this->hasCachedComponents()) {
|
||||
$this->queueLivewireComponentForRegistration(DatabaseNotifications::class);
|
||||
$this->queueLivewireComponentForRegistration(EditProfile::class);
|
||||
$this->queueLivewireComponentForRegistration(GlobalSearch::class);
|
||||
$this->queueLivewireComponentForRegistration(Notifications::class);
|
||||
|
||||
if ($this->hasEmailVerification() && is_subclass_of($emailVerificationRouteAction = $this->getEmailVerificationPromptRouteAction(), Component::class)) {
|
||||
$this->queueLivewireComponentForRegistration($emailVerificationRouteAction);
|
||||
}
|
||||
|
||||
if ($this->hasLogin() && is_subclass_of($loginRouteAction = $this->getLoginRouteAction(), Component::class)) {
|
||||
$this->queueLivewireComponentForRegistration($loginRouteAction);
|
||||
}
|
||||
|
||||
if ($this->hasPasswordReset()) {
|
||||
if (is_subclass_of($requestPasswordResetRouteAction = $this->getRequestPasswordResetRouteAction(), Component::class)) {
|
||||
$this->queueLivewireComponentForRegistration($requestPasswordResetRouteAction);
|
||||
if ($this->hasEmailVerification() && is_subclass_of($emailVerificationRouteAction = $this->getEmailVerificationPromptRouteAction(), Component::class)) {
|
||||
$this->queueLivewireComponentForRegistration($emailVerificationRouteAction);
|
||||
}
|
||||
|
||||
if (is_subclass_of($resetPasswordRouteAction = $this->getResetPasswordRouteAction(), Component::class)) {
|
||||
$this->queueLivewireComponentForRegistration($resetPasswordRouteAction);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->hasRegistration() && is_subclass_of($registrationRouteAction = $this->getRegistrationRouteAction(), Component::class)) {
|
||||
$this->queueLivewireComponentForRegistration($registrationRouteAction);
|
||||
}
|
||||
|
||||
foreach ($this->getResources() as $resource) {
|
||||
foreach ($resource::getPages() as $pageRegistration) {
|
||||
$this->queueLivewireComponentForRegistration($pageRegistration->getPage());
|
||||
if ($this->hasLogin() && is_subclass_of($loginRouteAction = $this->getLoginRouteAction(), Component::class)) {
|
||||
$this->queueLivewireComponentForRegistration($loginRouteAction);
|
||||
}
|
||||
|
||||
foreach ($resource::getRelations() as $relation) {
|
||||
if ($relation instanceof RelationGroup) {
|
||||
foreach ($relation->getManagers() as $groupedRelation) {
|
||||
$this->queueLivewireComponentForRegistration($this->normalizeRelationManagerClass($groupedRelation));
|
||||
}
|
||||
|
||||
continue;
|
||||
if ($this->hasPasswordReset()) {
|
||||
if (is_subclass_of($requestPasswordResetRouteAction = $this->getRequestPasswordResetRouteAction(), Component::class)) {
|
||||
$this->queueLivewireComponentForRegistration($requestPasswordResetRouteAction);
|
||||
}
|
||||
|
||||
$this->queueLivewireComponentForRegistration($this->normalizeRelationManagerClass($relation));
|
||||
if (is_subclass_of($resetPasswordRouteAction = $this->getResetPasswordRouteAction(), Component::class)) {
|
||||
$this->queueLivewireComponentForRegistration($resetPasswordRouteAction);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($resource::getWidgets() as $widget) {
|
||||
$this->queueLivewireComponentForRegistration($this->normalizeWidgetClass($widget));
|
||||
if ($this->hasRegistration() && is_subclass_of($registrationRouteAction = $this->getRegistrationRouteAction(), Component::class)) {
|
||||
$this->queueLivewireComponentForRegistration($registrationRouteAction);
|
||||
}
|
||||
|
||||
foreach ($this->getResources() as $resource) {
|
||||
foreach ($resource::getPages() as $pageRegistration) {
|
||||
$this->queueLivewireComponentForRegistration($pageRegistration->getPage());
|
||||
}
|
||||
|
||||
foreach ($resource::getRelations() as $relation) {
|
||||
if ($relation instanceof RelationGroup) {
|
||||
foreach ($relation->getManagers() as $groupedRelation) {
|
||||
$this->queueLivewireComponentForRegistration($this->normalizeRelationManagerClass($groupedRelation));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->queueLivewireComponentForRegistration($this->normalizeRelationManagerClass($relation));
|
||||
}
|
||||
|
||||
foreach ($resource::getWidgets() as $widget) {
|
||||
$this->queueLivewireComponentForRegistration($this->normalizeWidgetClass($widget));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->livewireComponents as $componentName => $componentClass) {
|
||||
Livewire::component($componentName, $componentClass);
|
||||
}
|
||||
|
||||
$this->livewireComponents = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -561,4 +599,78 @@ trait HasComponents
|
||||
|
||||
return $this->clusteredComponents[$cluster] ?? [];
|
||||
}
|
||||
|
||||
public function hasCachedComponents(): bool
|
||||
{
|
||||
return $this->hasCachedComponents ??= ((! app()->runningInConsole()) && app(Filesystem::class)->exists($this->getComponentCachePath()));
|
||||
}
|
||||
|
||||
public function cacheComponents(): void
|
||||
{
|
||||
$this->hasCachedComponents = false;
|
||||
|
||||
$cachePath = $this->getComponentCachePath();
|
||||
|
||||
$filesystem = app(Filesystem::class);
|
||||
|
||||
$filesystem->ensureDirectoryExists((string) str($cachePath)->beforeLast(DIRECTORY_SEPARATOR));
|
||||
|
||||
$filesystem->put(
|
||||
$cachePath,
|
||||
'<?php return ' . var_export([
|
||||
'livewireComponents' => $this->livewireComponents,
|
||||
'clusters' => $this->clusters,
|
||||
'clusteredComponents' => $this->clusteredComponents,
|
||||
'clusterDirectories' => $this->clusterDirectories,
|
||||
'clusterNamespaces' => $this->clusterNamespaces,
|
||||
'pages' => $this->pages,
|
||||
'pageDirectories' => $this->pageDirectories,
|
||||
'pageNamespaces' => $this->pageNamespaces,
|
||||
'resources' => $this->resources,
|
||||
'resourceDirectories' => $this->resourceDirectories,
|
||||
'resourceNamespaces' => $this->resourceNamespaces,
|
||||
'widgets' => $this->widgets,
|
||||
'widgetDirectories' => $this->widgetDirectories,
|
||||
'widgetNamespaces' => $this->widgetNamespaces,
|
||||
], true) . ';',
|
||||
);
|
||||
|
||||
$this->hasCachedComponents = true;
|
||||
}
|
||||
|
||||
public function restoreCachedComponents(): void
|
||||
{
|
||||
if (! $this->hasCachedComponents()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cache = require $this->getComponentCachePath();
|
||||
|
||||
$this->livewireComponents = $cache['livewireComponents'] ?? [];
|
||||
$this->clusters = $cache['clusters'] ?? [];
|
||||
$this->clusteredComponents = $cache['clusteredComponents'] ?? [];
|
||||
$this->clusterDirectories = $cache['clusterDirectories'] ?? [];
|
||||
$this->clusterNamespaces = $cache['clusterNamespaces'] ?? [];
|
||||
$this->pages = $cache['pages'] ?? [];
|
||||
$this->pageDirectories = $cache['pageDirectories'] ?? [];
|
||||
$this->pageNamespaces = $cache['pageNamespaces'] ?? [];
|
||||
$this->resources = $cache['resources'] ?? [];
|
||||
$this->resourceDirectories = $cache['resourceDirectories'] ?? [];
|
||||
$this->resourceNamespaces = $cache['resourceNamespaces'] ?? [];
|
||||
$this->widgets = $cache['widgets'] ?? [];
|
||||
$this->widgetDirectories = $cache['widgetDirectories'] ?? [];
|
||||
$this->widgetNamespaces = $cache['widgetNamespaces'] ?? [];
|
||||
}
|
||||
|
||||
public function clearCachedComponents(): void
|
||||
{
|
||||
app(Filesystem::class)->delete($this->getComponentCachePath());
|
||||
|
||||
$this->hasCachedComponents = false;
|
||||
}
|
||||
|
||||
public function getComponentCachePath(): string
|
||||
{
|
||||
return (config('filament.cache_path') ?? base_path('bootstrap/cache/filament')) . DIRECTORY_SEPARATOR . "panels/{$this->getId()}.php";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,31 @@
|
||||
|
||||
namespace Filament\Panel\Concerns;
|
||||
|
||||
use Exception;
|
||||
|
||||
trait HasId
|
||||
{
|
||||
protected string $id;
|
||||
|
||||
public function id(string $id): static
|
||||
{
|
||||
if (isset($this->id)) {
|
||||
throw new Exception("The panel has already been registered with the ID [{$this->id}].");
|
||||
}
|
||||
|
||||
$this->id = $id;
|
||||
$this->configure();
|
||||
$this->restoreCachedComponents();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
if (! isset($this->id)) {
|
||||
throw new Exception('A panel has been registered without an `id()`.');
|
||||
}
|
||||
|
||||
return $this->id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,10 @@ namespace Filament\Panel\Concerns;
|
||||
use Closure;
|
||||
use Filament\Navigation\NavigationGroup;
|
||||
use Filament\Navigation\NavigationItem;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Filament\Navigation\NavigationManager;
|
||||
|
||||
trait HasNavigation
|
||||
{
|
||||
protected bool $isNavigationMounted = false;
|
||||
|
||||
/**
|
||||
* @var array<string | int, NavigationGroup | string>
|
||||
*/
|
||||
@@ -24,6 +21,8 @@ trait HasNavigation
|
||||
|
||||
protected Closure | bool $navigationBuilder = true;
|
||||
|
||||
protected ?NavigationManager $navigationManager = null;
|
||||
|
||||
public function navigation(Closure | bool $builder = true): static
|
||||
{
|
||||
$this->navigationBuilder = $builder;
|
||||
@@ -42,24 +41,17 @@ trait HasNavigation
|
||||
return $builder->getNavigation();
|
||||
}
|
||||
|
||||
public function mountNavigation(): void
|
||||
{
|
||||
foreach ($this->getPages() as $page) {
|
||||
$page::registerNavigationItems();
|
||||
}
|
||||
|
||||
foreach ($this->getResources() as $resource) {
|
||||
$resource::registerNavigationItems();
|
||||
}
|
||||
|
||||
$this->isNavigationMounted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string | int, NavigationGroup | string> $groups
|
||||
*/
|
||||
public function navigationGroups(array $groups): static
|
||||
{
|
||||
if (isset($this->navigationManager)) {
|
||||
$this->navigationManager->navigationGroups($groups);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->navigationGroups = [
|
||||
...$this->navigationGroups,
|
||||
...$groups,
|
||||
@@ -73,6 +65,12 @@ trait HasNavigation
|
||||
*/
|
||||
public function navigationItems(array $items): static
|
||||
{
|
||||
if (isset($this->navigationManager)) {
|
||||
$this->navigationManager->navigationItems($items);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->navigationItems = [
|
||||
...$this->navigationItems,
|
||||
...$items,
|
||||
@@ -86,95 +84,23 @@ trait HasNavigation
|
||||
return $this->navigationBuilder !== false;
|
||||
}
|
||||
|
||||
public function hasNavigationBuilder(): bool
|
||||
{
|
||||
return $this->navigationBuilder instanceof Closure;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<NavigationGroup>
|
||||
*/
|
||||
public function getNavigation(): array
|
||||
{
|
||||
if ($this->navigationBuilder instanceof Closure) {
|
||||
return $this->buildNavigation();
|
||||
$this->navigationManager = app(NavigationManager::class);
|
||||
|
||||
try {
|
||||
return app(NavigationManager::class)->get();
|
||||
} finally {
|
||||
$this->navigationManager = null;
|
||||
}
|
||||
|
||||
if (! $this->isNavigationMounted) {
|
||||
$this->mountNavigation();
|
||||
}
|
||||
|
||||
$groups = collect($this->getNavigationGroups());
|
||||
|
||||
return collect($this->getNavigationItems())
|
||||
->filter(fn (NavigationItem $item): bool => $item->isVisible())
|
||||
->sortBy(fn (NavigationItem $item): int => $item->getSort())
|
||||
->groupBy(fn (NavigationItem $item): ?string => $item->getGroup())
|
||||
->map(function (Collection $items, ?string $groupIndex) use ($groups): NavigationGroup {
|
||||
$parentItems = $items->groupBy(fn (NavigationItem $item): ?string => $item->getParentItem());
|
||||
|
||||
$items = $parentItems->get('')
|
||||
->keyBy(fn (NavigationItem $item): string => $item->getLabel());
|
||||
|
||||
$parentItems->except([''])->each(function (Collection $parentItemItems, string $parentItemLabel) use ($items) {
|
||||
if (! $items->has($parentItemLabel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$items->get($parentItemLabel)->childItems($parentItemItems);
|
||||
});
|
||||
|
||||
if (blank($groupIndex)) {
|
||||
return NavigationGroup::make()->items($items);
|
||||
}
|
||||
|
||||
$registeredGroup = $groups
|
||||
->first(function (NavigationGroup | string $registeredGroup, string | int $registeredGroupIndex) use ($groupIndex) {
|
||||
if ($registeredGroupIndex === $groupIndex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($registeredGroup === $groupIndex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (! $registeredGroup instanceof NavigationGroup) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $registeredGroup->getLabel() === $groupIndex;
|
||||
});
|
||||
|
||||
if ($registeredGroup instanceof NavigationGroup) {
|
||||
return $registeredGroup->items($items);
|
||||
}
|
||||
|
||||
return NavigationGroup::make($registeredGroup ?? $groupIndex)
|
||||
->items($items);
|
||||
})
|
||||
->sortBy(function (NavigationGroup $group, ?string $groupIndex): int {
|
||||
if (blank($group->getLabel())) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
$registeredGroups = $this->getNavigationGroups();
|
||||
|
||||
$groupsToSearch = $registeredGroups;
|
||||
|
||||
if (Arr::first($registeredGroups) instanceof NavigationGroup) {
|
||||
$groupsToSearch = [
|
||||
...array_keys($registeredGroups),
|
||||
...array_map(fn (NavigationGroup $registeredGroup): string => $registeredGroup->getLabel(), array_values($registeredGroups)),
|
||||
];
|
||||
}
|
||||
|
||||
$sort = array_search(
|
||||
$groupIndex,
|
||||
$groupsToSearch,
|
||||
);
|
||||
|
||||
if ($sort === false) {
|
||||
return count($registeredGroups);
|
||||
}
|
||||
|
||||
return $sort;
|
||||
})
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Filament\Panel\Concerns;
|
||||
|
||||
use Closure;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Navigation\NavigationManager;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Arr;
|
||||
use Laravel\SerializableClosure\Serializers\Native;
|
||||
@@ -155,19 +156,11 @@ trait HasRoutes
|
||||
if ($tenant) {
|
||||
$originalTenant = Filament::getTenant();
|
||||
Filament::setTenant($tenant, isQuiet: true);
|
||||
|
||||
$isNavigationMountedOriginally = $this->isNavigationMounted;
|
||||
$originalNavigationItems = $this->navigationItems;
|
||||
$originalNavigationGroups = $this->navigationGroups;
|
||||
|
||||
$this->isNavigationMounted = false;
|
||||
$this->navigationItems = [];
|
||||
$this->navigationGroups = [];
|
||||
|
||||
$navigation = $this->getNavigation();
|
||||
}
|
||||
|
||||
$navigation = $this->getNavigation();
|
||||
$this->navigationManager = new NavigationManager();
|
||||
|
||||
$navigation = $this->navigationManager->get();
|
||||
|
||||
try {
|
||||
$firstGroup = Arr::first($navigation);
|
||||
@@ -186,11 +179,9 @@ trait HasRoutes
|
||||
} finally {
|
||||
if ($tenant) {
|
||||
Filament::setTenant($originalTenant, isQuiet: true);
|
||||
|
||||
$this->isNavigationMounted = $isNavigationMountedOriginally;
|
||||
$this->navigationItems = $originalNavigationItems;
|
||||
$this->navigationGroups = $originalNavigationGroups;
|
||||
}
|
||||
|
||||
$this->navigationManager = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Filament;
|
||||
|
||||
use Filament\Exceptions\NoDefaultPanelSetException;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class PanelRegistry
|
||||
{
|
||||
/**
|
||||
* @var array<string, Panel>
|
||||
*/
|
||||
public array $panels = [];
|
||||
|
||||
public function register(Panel $panel): void
|
||||
{
|
||||
$this->panels[$panel->getId()] = $panel;
|
||||
|
||||
$panel->register();
|
||||
|
||||
if (! $panel->isDefault()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (app()->resolved('filament')) {
|
||||
app('filament')->setCurrentPanel($panel);
|
||||
}
|
||||
|
||||
app()->resolving(
|
||||
'filament',
|
||||
fn (FilamentManager $manager) => $manager->setCurrentPanel($panel),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NoDefaultPanelSetException
|
||||
*/
|
||||
public function getDefault(): Panel
|
||||
{
|
||||
return Arr::first(
|
||||
$this->panels,
|
||||
fn (Panel $panel): bool => $panel->isDefault(),
|
||||
fn () => throw NoDefaultPanelSetException::make(),
|
||||
);
|
||||
}
|
||||
|
||||
public function get(?string $id = null): Panel
|
||||
{
|
||||
return $this->panels[$id] ?? $this->getDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, Panel>
|
||||
*/
|
||||
public function all(): array
|
||||
{
|
||||
return $this->panels;
|
||||
}
|
||||
}
|
||||
@@ -613,13 +613,8 @@ abstract class Resource
|
||||
|
||||
public static function routes(Panel $panel): void
|
||||
{
|
||||
$slug = static::getSlug();
|
||||
$routeBaseName = (string) str($slug)
|
||||
->replace('/', '.')
|
||||
->append('.');
|
||||
|
||||
Route::name($routeBaseName)
|
||||
->prefix($slug)
|
||||
Route::name(static::getRelativeRouteName() . '.')
|
||||
->prefix(static::getRoutePrefix())
|
||||
->middleware(static::getRouteMiddleware($panel))
|
||||
->withoutMiddleware(static::getWithoutRouteMiddleware($panel))
|
||||
->group(function () use ($panel) {
|
||||
@@ -629,6 +624,16 @@ abstract class Resource
|
||||
});
|
||||
}
|
||||
|
||||
public static function getRelativeRouteName(): string
|
||||
{
|
||||
return (string) str(static::getSlug())->replace('/', '.');
|
||||
}
|
||||
|
||||
public static function getRoutePrefix(): string
|
||||
{
|
||||
return static::getSlug();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string | array<string>
|
||||
*/
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user