├── src ├── JsonInfolist.php ├── FilamentJsonColumnServiceProvider.php └── JsonColumn.php ├── resources ├── views │ ├── components │ │ ├── json-viewer.blade.php │ │ ├── json-toggle.blade.php │ │ └── json-editor-content.blade.php │ ├── infolist.blade.php │ └── index.blade.php ├── js │ └── filament-json-column.js └── css │ └── filament-json-column.css ├── phpunit.xml ├── LICENSE.md ├── composer.json └── README.md /src/JsonInfolist.php: -------------------------------------------------------------------------------- 1 | 4 |

5 | 
6 | 


--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 7 |     
 8 |         
 9 |             ./tests
10 |         
11 |     
12 |     
13 |         
14 |             ./src
15 |         
16 |     
17 | 
18 | 


--------------------------------------------------------------------------------
/resources/views/infolist.blade.php:
--------------------------------------------------------------------------------
 1 | 
 5 |     
true, 17 | 'box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);', 18 | 'overflow: hidden;' 19 | ]) class="fi-json-column master"> 20 |
21 |

22 |         
23 |
24 |
25 | -------------------------------------------------------------------------------- /resources/js/filament-json-column.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Pretty Print JSON Objects. 3 | * Inspired by http://jsfiddle.net/unLSJ/ 4 | * 5 | * @return {string} html string of the formatted JS object 6 | * @example: var obj = {"foo":"bar"}; obj.prettyPrint(); 7 | */ 8 | window.prettyPrint = function (json) { 9 | var jsonLine = /^( *)("[\w]+": )?("[^"]*"|[\w.+-]*|\[\])?([,[{])?$/mg; 10 | var replacer = function (match, pIndent, pKey, pVal, pEnd) { 11 | var key = '', 12 | val = '', 13 | str = '', 14 | r = pIndent || ''; 15 | if (pKey) 16 | r = r + key + pKey.replace(/[": ]/g, '') + ': '; 17 | if (pVal) 18 | r = r + (pVal[0] == '"' ? str : val) + pVal + ''; 19 | return r + (pEnd || ''); 20 | }; 21 | 22 | return JSON.stringify(json, null, 3) 23 | .replace(/&/g, '&').replace(/\\"/g, '"') 24 | .replace(//g, '>') 25 | .replace(jsonLine, replacer); 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) valentin-morice 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /resources/views/components/json-toggle.blade.php: -------------------------------------------------------------------------------- 1 |
9 |
10 |
11 |
19 |
Viewer
20 |
21 |
27 |
Editor
28 |
29 |
30 |
31 |
32 | -------------------------------------------------------------------------------- /src/FilamentJsonColumnServiceProvider.php: -------------------------------------------------------------------------------- 1 | name(static::$name) 25 | ->hasViews(); 26 | } 27 | 28 | public function packageBooted(): void 29 | { 30 | FilamentAsset::register([ 31 | Js::make('jsoneditor-js-cdn', 'https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/10.0.2/jsoneditor.min.js'), 32 | Js::make('filament-json-column-js', __DIR__.'/../resources/js/filament-json-column.js'), 33 | Css::make('filament-json-column', __DIR__.'/../resources/css/filament-json-column.css'), 34 | Css::make('jsoneditor-css-cdn', 'https://cdnjs.cloudflare.com/ajax/libs/jsoneditor/10.0.2/jsoneditor.min.css'), 35 | ], 'filament-json-column'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /resources/css/filament-json-column.css: -------------------------------------------------------------------------------- 1 | .fi-json-column pre.prettyjson { 2 | color: black; 3 | background-color: ghostwhite; 4 | padding: 20px 25px; 5 | overflow: auto; 6 | } 7 | 8 | :is(.dark) .fi-json-column pre.prettyjson { 9 | opacity: .7; 10 | --tw-bg-opacity: 1; 11 | --tw-border-opacity: 1; 12 | background-color: #161617; 13 | color: rgb(209 213 219/var(--tw-text-opacity)); 14 | } 15 | 16 | :is(.dark) .fi-json-column pre.prettyjson span.json-key { 17 | color: red !important; 18 | } 19 | 20 | :is(.dark) .fi-json-column pre.prettyjson span.json-string { 21 | color: aquamarine !important; 22 | } 23 | 24 | :is(.dark) .fi-json-column pre.prettyjson span.json-value { 25 | color: deepskyblue !important; 26 | } 27 | 28 | .master.fi-json-column { 29 | border: #e5e7eb solid 1px; 30 | } 31 | 32 | .fi-json-column .control { 33 | cursor: pointer; 34 | padding: 0.875rem 1.25rem; 35 | } 36 | 37 | :is(.dark) .fi-json-column .control:hover { 38 | background-color: #161617; 39 | } 40 | 41 | .fi-json-column .control:hover { 42 | background-color: #F9FAFB; 43 | } 44 | 45 | .fi-json-column .container { 46 | display: flex; 47 | justify-content: space-between; 48 | align-items: center; 49 | background-color: white; 50 | border-bottom: #e5e7eb solid 1px; 51 | } 52 | 53 | :is(.dark) .fi-json-column .container { 54 | background-color: transparent !important; 55 | border-bottom: hsla(0,0%,100%,.2) solid 1px !important; 56 | } 57 | 58 | :is(.dark) .fi-json-column .master { 59 | border: hsla(0,0%,100%,.2) solid 1px !important; 60 | } 61 | 62 | .fi-json-column .active { 63 | background-color: #F9FAFB; 64 | } 65 | 66 | :is(.dark) .fi-json-column .active { 67 | background-color: #161617 !important; 68 | } 69 | -------------------------------------------------------------------------------- /resources/views/index.blade.php: -------------------------------------------------------------------------------- 1 | 5 | @php 6 | $uniqid = uniqid(); 7 | $is_default = ! $getEditorMode() && ! $getViewerMode(); 8 | $display = $getViewerMode() ? 'viewer' : 'editor'; 9 | @endphp 10 | 11 |
$is_default, 14 | 'border-radius: 0' => $getEditorMode() === true, 15 | 'border-radius: 0.5rem;' => $getViewerMode() === true, 16 | 'box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);', 17 | 'overflow: hidden;' 18 | ]) 19 | x-data="{ 20 | id: {{ \Illuminate\Support\Js::from($uniqid) }}, 21 | state: $wire.entangle({{ \Illuminate\Support\Js::from($getStatePath()) }}), 22 | get prettyJson() { 23 | try { 24 | return window.prettyPrint(JSON.parse(this.state)); 25 | } catch { 26 | return window.prettyPrint(this.state); 27 | } 28 | }, 29 | display: {{ \Illuminate\Support\Js::from($is_default ? 'viewer' : $display) }} 30 | }" 31 | > 32 | @if($is_default) 33 | @include('filament-json-column::components.json-toggle') 34 | @endif 35 | @if($is_default || $getEditorMode()) 36 | @include('filament-json-column::components.json-editor-content', 37 | ['uniqid' => $uniqid, 'height' => $getEditorHeight(), 'modes' => $getModes()] 38 | ) 39 | @endif 40 | @if($is_default || $getViewerMode()) 41 | @include('filament-json-column::components.json-viewer', 42 | ['uniqid' => $uniqid, 'height' => $getViewerHeight()] 43 | ) 44 | @endif 45 |
46 | 51 |
52 | -------------------------------------------------------------------------------- /resources/views/components/json-editor-content.blade.php: -------------------------------------------------------------------------------- 1 | @props(['uniqid', 'height', 'modes']) 2 | 3 |
4 |
58 |
59 |
60 |
61 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "valentin-morice/filament-json-column", 3 | "description": "A simple package to view and edit your JSON columns in Filament", 4 | "keywords": [ 5 | "valentin-morice", 6 | "laravel", 7 | "filament-json-column" 8 | ], 9 | "homepage": "https://github.com/valentin-morice/filament-json-column", 10 | "support": { 11 | "issues": "https://github.com/valentin-morice/filament-json-column/issues", 12 | "source": "https://github.com/valentin-morice/filament-json-column" 13 | }, 14 | "license": "MIT", 15 | "authors": [ 16 | { 17 | "name": "valentin-morice", 18 | "email": "valentinmorice1@gmail.com", 19 | "role": "Developer" 20 | } 21 | ], 22 | "require": { 23 | "php": "^8.1", 24 | "filament/forms": "^3.0", 25 | "filament/notifications": "^3.3", 26 | "spatie/laravel-package-tools": "^1.15.0" 27 | }, 28 | "require-dev": { 29 | "laravel/pint": "^1.0", 30 | "nunomaduro/collision": "^7.9", 31 | "nunomaduro/larastan": "^2.0.1", 32 | "orchestra/testbench": "^8.0", 33 | "pestphp/pest": "^2.1", 34 | "pestphp/pest-plugin-arch": "^2.0", 35 | "pestphp/pest-plugin-laravel": "^2.0", 36 | "phpstan/extension-installer": "^1.1", 37 | "phpstan/phpstan-deprecation-rules": "^1.0", 38 | "phpstan/phpstan-phpunit": "^1.0" 39 | }, 40 | "autoload": { 41 | "psr-4": { 42 | "ValentinMorice\\FilamentJsonColumn\\": "src/" 43 | } 44 | }, 45 | "autoload-dev": { 46 | "psr-4": { 47 | "ValentinMorice\\FilamentJsonColumn\\Tests\\": "tests/" 48 | } 49 | }, 50 | "scripts": { 51 | "post-autoload-dump": "@php ./vendor/bin/testbench package:discover --ansi", 52 | "analyse": "vendor/bin/phpstan analyse", 53 | "test": "vendor/bin/pest", 54 | "test-coverage": "vendor/bin/pest --coverage", 55 | "format": "vendor/bin/pint" 56 | }, 57 | "config": { 58 | "sort-packages": true, 59 | "allow-plugins": { 60 | "pestphp/pest-plugin": true, 61 | "phpstan/extension-installer": true 62 | } 63 | }, 64 | "extra": { 65 | "laravel": { 66 | "providers": [ 67 | "ValentinMorice\\FilamentJsonColumn\\FilamentJsonColumnServiceProvider" 68 | ], 69 | "aliases": { 70 | "FilamentJsonColumn": "ValentinMorice\\FilamentJsonColumn\\Facades\\FilamentJsonColumn" 71 | } 72 | } 73 | }, 74 | "minimum-stability": "dev", 75 | "prefer-stable": true 76 | } 77 | -------------------------------------------------------------------------------- /src/JsonColumn.php: -------------------------------------------------------------------------------- 1 | rules([ 31 | fn (): Closure => function (string $attribute, $value, Closure $fail) { 32 | $rule = is_array($value) || (is_string($value) && json_validate($value)); 33 | 34 | if (! $rule) { 35 | $fail(__('validation.json')); 36 | } 37 | }, 38 | ]); 39 | 40 | $this->afterStateHydrated(function (JsonColumn $component, $state) { 41 | if (is_array($state)) { 42 | $component->state(json_encode($state, JSON_FORCE_OBJECT)); 43 | } 44 | }); 45 | 46 | $this->beforeStateDehydrated(function (JsonColumn $component, $state) { 47 | if (is_string($state)) { 48 | $component->state(json_decode($state, true)); 49 | } 50 | }); 51 | } 52 | 53 | public function getEditorHeight(): string 54 | { 55 | return $this->evaluate($this->editorHeight).'px'; 56 | } 57 | 58 | public function getViewerHeight(): string 59 | { 60 | return $this->evaluate($this->viewerHeight).'px'; 61 | } 62 | 63 | public function getAccent(): string 64 | { 65 | return $this->evaluate($this->accent); 66 | } 67 | 68 | public function getEditorMode(): bool 69 | { 70 | return $this->evaluate($this->editorMode); 71 | } 72 | 73 | public function getViewerMode(): bool 74 | { 75 | return $this->evaluate($this->viewerMode); 76 | } 77 | 78 | public function getModes(): array 79 | { 80 | return $this->evaluate($this->modes); 81 | } 82 | 83 | public function editorOnly(Closure|bool $bool = true): static 84 | { 85 | $this->editorMode = $bool; 86 | 87 | return $this; 88 | } 89 | 90 | public function viewerOnly(Closure|bool $bool = true): static 91 | { 92 | $this->viewerMode = $bool; 93 | 94 | return $this; 95 | } 96 | 97 | public function editorHeight(Closure|int $heightInPx): static 98 | { 99 | $this->editorHeight = $heightInPx; 100 | 101 | return $this; 102 | } 103 | 104 | public function viewerHeight(Closure|int $heightInPx): static 105 | { 106 | $this->viewerHeight = $heightInPx; 107 | 108 | return $this; 109 | } 110 | 111 | /** 112 | * @throws \Exception 113 | */ 114 | public function editorModes(Closure|array $modes): static 115 | { 116 | foreach ((array) $modes as $mode) { 117 | if (! in_array($mode, $this->modes, true)) { 118 | throw new \Exception('Invalid parameter: '.json_encode($modes).'. Allowed values are: '.implode(', ', $this->modes)); 119 | } 120 | } 121 | 122 | $this->modes = $modes; 123 | 124 | return $this; 125 | } 126 | 127 | public function accent(Closure|string $hexcode): static 128 | { 129 | $this->accent = $hexcode; 130 | 131 | return $this; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # filament-json-column 2 | 3 | A simple package to view and edit your JSON columns in Filament. 4 | 5 | ![image](https://github.com/valentin-morice/filament-json-column/assets/100000204/41212480-f635-4d50-b967-cad5dbda6dc9) 6 | ![image](https://github.com/valentin-morice/filament-json-column/assets/100000204/29591beb-524b-4671-b4ea-d5ec6b1f5705) 7 | 8 | ## Installation (Stable) 9 | 10 | You can install the stable version of the package via composer: 11 | 12 | ```bash 13 | composer require valentin-morice/filament-json-column 14 | ``` 15 | 16 | ## Pre-Release / Dev Version (v3.0) 17 | 18 | To try out the upcoming v3.0, compatible with Filament v4, you can require the `dev` branch directly. 19 | 20 | Thanks to [@safwendammak](https://github.com/safwendammak) for his pull-request. 21 | 22 | **Note: This branch is under active development and may contain unstable code.** 23 | 24 | Add the following to your `composer.json`: 25 | 26 | ```json 27 | { 28 | "require": { 29 | "valentin-morice/filament-json-column": "dev-dev" 30 | }, 31 | "minimum-stability": "dev", 32 | "prefer-stable": true 33 | } 34 | ``` 35 | 36 | Then run `composer update`: 37 | 38 | ```bash 39 | composer update valentin-morice/filament-json-column 40 | ``` 41 | 42 | ## Usage 43 | 44 | The `filament-json-column` plugin works as any other Filament Form Builder or Infolist classes. Make sure the column on which it is called is cast to **JSON** or **array** within your Eloquent model. 45 | 46 | ```php 47 | use ValentinMorice\FilamentJsonColumn\JsonColumn; 48 | use ValentinMorice\FilamentJsonColumn\JsonInfolist; 49 | 50 | public static function form(Form $form): Form 51 | { 52 | return $form 53 | ->schema([ 54 | JsonColumn::make('example'), 55 | ]); 56 | } 57 | 58 | // An infolist component is also available. 59 | public static function infolist(Infolist $infolist): Infolist 60 | { 61 | return $infolist 62 | ->schema([ 63 | JsonInfolist::make('example'), 64 | ]); 65 | } 66 | ``` 67 | 68 | The form component provides you with two tabs: `Viewer` & `Editor`. The `Viewer` tab pretty prints your JSON data, while the `Editor` tab lets you edit it conveniently. 69 | All the methods provided by the plugin accept closures, injected with standard Filament [utilities](https://filamentphp.com/docs/3.x/forms/advanced#form-component-utility-injection). 70 | 71 | ### Personalize the accent color 72 | The tab selector menu uses the `slateblue` CSS color by default. However, you can choose any other color: 73 | ```php 74 | JsonColumn::make('example')->accent(string '#FFFFFF'|Closure); // The input needs to be a valid CSS color 75 | ``` 76 | 77 | ### Display a single tab 78 | 79 | If you'd like to use only one of the tabs, without giving your user the possibility to switch to another, use the following methods: 80 | ```php 81 | JsonColumn::make('example')->editorOnly(bool|Closure); // Displays only the editor tab 82 | JsonColumn::make('example')->viewerOnly(bool|Closure); // Displays only the viewer tab 83 | ``` 84 | 85 | ### Change the height 86 | 87 | ```php 88 | JsonColumn::make('example')->editorHeight(int 500|Closure); // Accepts an int, defaults to 300 89 | JsonColumn::make('example')->viewerHeight(int 500|Closure); // Accepts an int, defaults to 300 90 | ``` 91 | 92 | ### Editor modes 93 | Customize the editor modes. Accepted values (and default) are: `['code', 'form', 'text', 'tree', 'view', 'preview']` 94 | ```php 95 | JsonColumn::make('example')->modes(array|Closure ['code', 'text', 'tree']); 96 | ``` 97 | 98 | ### Validation 99 | 100 | Values are validated as proper JSON by default. 101 | 102 | ## Credits 103 | I've taken inspiration from the following plugins: [Pretty JSON](https://github.com/novadaemon/filament-pretty-json) & [JSONeditor](https://github.com/invaders-xx/filament-jsoneditor). 104 | 105 | ## License 106 | 107 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 108 | --------------------------------------------------------------------------------