@jdanino/inertiajs-tables-laravel-query-builder 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Protone Media
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 all
13
+ 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 THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,503 @@
1
+ # Inertia.js Tables for Laravel Query Builder
2
+
3
+ [![Latest Version on NPM](https://img.shields.io/npm/v/@protonemedia/inertiajs-tables-laravel-query-builder.svg?style=flat-square)](https://npmjs.com/package/@protonemedia/inertiajs-tables-laravel-query-builder)
4
+ [![npm](https://img.shields.io/npm/dt/@protonemedia/inertiajs-tables-laravel-query-builder.svg?style=flat-square)](https://www.npmjs.com/package/@protonemedia/inertiajs-tables-laravel-query-builder)
5
+ [![Latest Version on Packagist](https://img.shields.io/packagist/v/protonemedia/inertiajs-tables-laravel-query-builder.svg?style=flat-square)](https://packagist.org/packages/protonemedia/inertiajs-tables-laravel-query-builder)
6
+ [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md)
7
+ [![run-tests](https://github.com/protonemedia/inertiajs-tables-laravel-query-builder/actions/workflows/php.yml/badge.svg?branch=main)](https://github.com/protonemedia/inertiajs-tables-laravel-query-builder/actions/workflows/php.yml)
8
+
9
+ This package provides a *DataTables-like* experience for [Inertia.js](https://inertiajs.com/) with support for searching, filtering, sorting, toggling columns, and pagination. It generates URLs that can be consumed by Spatie's excellent [Laravel Query Builder](https://github.com/spatie/laravel-query-builder) package, with no additional logic needed. The components are styled with [Tailwind CSS 3.0](https://tailwindcss.com/), but it's fully customizable with slots. The data refresh logic is based on Inertia's [Ping CRM demo](https://github.com/inertiajs/pingcrm).
10
+
11
+ ![Inertia.js Table for Laravel Query Builder](https://user-images.githubusercontent.com/8403149/177773377-86c32d69-8f86-47e4-8063-ea227e480d10.mp4)
12
+
13
+ ## Sponsorship Support
14
+
15
+ We proudly support the community by developing Laravel packages and giving them away for free. Keeping track of issues and pull requests takes time, but we're happy to help! If this package saves you time or if you're relying on it professionally, please consider [supporting the maintenance and development](https://github.com/sponsors/pascalbaljet).
16
+
17
+ ## Features
18
+
19
+ * Auto-fill: auto generates `thead` and `tbody` with support for custom cells
20
+ * Global Search
21
+ * Search per field
22
+ * Select filters
23
+ * Toggle columns
24
+ * Sort columns
25
+ * Pagination (support for Eloquent/API Resource/Simple/Cursor)
26
+ * Automatically updates the query string (by using [Inertia's replace](https://inertiajs.com/manual-visits#browser-history) feature)
27
+
28
+ ## Compatibility
29
+
30
+ * [Vue 3](https://v3.vuejs.org/guide/installation.html)
31
+ * [Laravel 9](https://laravel.com/)
32
+ * [Inertia.js](https://inertiajs.com/)
33
+ * [Tailwind CSS v3](https://tailwindcss.com/) + [Forms plugin](https://github.com/tailwindlabs/tailwindcss-forms)
34
+ * PHP 8.0+
35
+
36
+ **Note**: There is currently an [issue](https://github.com/protonemedia/inertiajs-tables-laravel-query-builder/issues/69) with using this package with Vite!
37
+
38
+ ## Installation
39
+
40
+ You need to install both the server-side package and the client-side package. Note that this package is only compatible with Laravel 9, Vue 3.0, and requires the Tailwind Forms plugin.
41
+
42
+ ### Server-side installation (Laravel)
43
+
44
+ You can install the package via composer:
45
+
46
+ ```bash
47
+ composer require protonemedia/inertiajs-tables-laravel-query-builder
48
+ ```
49
+
50
+ The package will automatically register the Service Provider which provides a `table` method you can use on an Interia Response.
51
+
52
+ #### Search fields
53
+
54
+ With the `searchInput` method, you can specify which attributes are searchable. Search queries are passed to the URL query as a `filter`. This integrates seamlessly with the [filtering feature](https://spatie.be/docs/laravel-query-builder/v5/features/filtering) of the Laravel Query Builder package.
55
+
56
+
57
+ Though it's enough to pass in the column key, you may specify a custom label and default value.
58
+
59
+ ```php
60
+ use ProtoneMedia\LaravelQueryBuilderInertiaJs\InertiaTable;
61
+
62
+ Inertia::render('Page/Index')->table(function (InertiaTable $table) {
63
+ $table->searchInput('name');
64
+
65
+ $table->searchInput(
66
+ key: 'framework',
67
+ label: 'Find your framework',
68
+ defaultValue: 'Laravel'
69
+ );
70
+ });
71
+ ```
72
+
73
+ #### Select Filters
74
+
75
+ Select Filters are similar to search fields but use a `select` element instead of an `input` element. This way, you can present the user a predefined set of options. Under the hood, this uses the same filtering feature of the Laravel Query Builder package.
76
+
77
+ The `selectFilter` method requires two arguments: the key, and a key-value array with the options.
78
+
79
+ ```php
80
+ Inertia::render('Page/Index')->table(function (InertiaTable $table) {
81
+ $table->selectFilter('language_code', [
82
+ 'en' => 'Engels',
83
+ 'nl' => 'Nederlands',
84
+ ]);
85
+ });
86
+ ```
87
+
88
+ The `selectFilter` will, by default, add a *no filter* option to the array. You may disable this or specify a custom label for it.
89
+
90
+ ```php
91
+ Inertia::render('Page/Index')->table(function (InertiaTable $table) {
92
+ $table->selectFilter(
93
+ key: 'language_code',
94
+ options: $languages,
95
+ label: 'Language',
96
+ defaultValue: 'nl',
97
+ noFilterOption: true
98
+ noFilterOptionLabel: 'All languages'
99
+ );
100
+ });
101
+ ```
102
+
103
+ #### Columns
104
+
105
+ With the `column` method, you can specify which columns you want to be toggleable, sortable, and searchable. You must pass in at least a key or label for each column.
106
+
107
+ ```php
108
+ Inertia::render('Page/Index')->table(function (InertiaTable $table) {
109
+ $table->column('name', 'User Name');
110
+
111
+ $table->column(
112
+ key: 'name',
113
+ label: 'User Name',
114
+ canBeHidden: true,
115
+ hidden: false,
116
+ sortable: true,
117
+ searchable: true
118
+ );
119
+ });
120
+ ```
121
+
122
+ The `searchable` option is a shortcut to the `searchInput` method. The example below will essentially call `$table->searchInput('name', 'User Name')`.
123
+
124
+ #### Global Search
125
+
126
+ You may enable Global Search with the `withGlobalSearch` method, and optionally specify a placeholder.
127
+
128
+ ```php
129
+ Inertia::render('Page/Index')->table(function (InertiaTable $table) {
130
+ $table->withGlobalSearch();
131
+
132
+ $table->withGlobalSearch('Search through the data...');
133
+ });
134
+ ```
135
+
136
+ If you want to enable Global Search for every table by default, you may use the static `defaultGlobalSearch` method, for example, in the `AppServiceProvider` class:
137
+
138
+ ```php
139
+ InertiaTable::defaultGlobalSearch();
140
+ InertiaTable::defaultGlobalSearch('Default custom placeholder');
141
+ InertiaTable::defaultGlobalSearch(false); // disable
142
+ ```
143
+
144
+ #### Example controller
145
+
146
+ ```php
147
+ <?php
148
+
149
+ namespace App\Http\Controllers;
150
+
151
+ use App\Models\User;
152
+ use Illuminate\Support\Collection;
153
+ use Inertia\Inertia;
154
+ use ProtoneMedia\LaravelQueryBuilderInertiaJs\InertiaTable;
155
+ use Spatie\QueryBuilder\AllowedFilter;
156
+ use Spatie\QueryBuilder\QueryBuilder;
157
+
158
+ class UserIndexController
159
+ {
160
+ public function __invoke()
161
+ {
162
+ $globalSearch = AllowedFilter::callback('global', function ($query, $value) {
163
+ $query->where(function ($query) use ($value) {
164
+ Collection::wrap($value)->each(function ($value) use ($query) {
165
+ $query
166
+ ->orWhere('name', 'LIKE', "%{$value}%")
167
+ ->orWhere('email', 'LIKE', "%{$value}%");
168
+ });
169
+ });
170
+ });
171
+
172
+ $users = QueryBuilder::for(User::class)
173
+ ->defaultSort('name')
174
+ ->allowedSorts(['name', 'email', 'language_code'])
175
+ ->allowedFilters(['name', 'email', 'language_code', $globalSearch])
176
+ ->paginate()
177
+ ->withQueryString();
178
+
179
+ return Inertia::render('Users/Index', [
180
+ 'users' => $users,
181
+ ])->table(function (InertiaTable $table) {
182
+ $table
183
+ ->withGlobalSearch()
184
+ ->defaultSort('name')
185
+ ->column(key: 'name', searchable: true, sortable: true, canBeHidden: false)
186
+ ->column(key: 'email', searchable: true, sortable: true)
187
+ ->column(key: 'language_code', label: 'Language')
188
+ ->column(label: 'Actions')
189
+ ->selectFilter(key: 'language_code', label: 'Language', options: [
190
+ 'en' => 'English',
191
+ 'nl' => 'Dutch',
192
+ ]);
193
+ }
194
+ }
195
+ ```
196
+
197
+ ### Client-side installation (Inertia)
198
+
199
+ You can install the package via either `npm` or `yarn`:
200
+
201
+ ```bash
202
+ npm install @protonemedia/inertiajs-tables-laravel-query-builder --save
203
+
204
+ yarn add @protonemedia/inertiajs-tables-laravel-query-builder
205
+ ```
206
+
207
+ Add the repository path to the `content` array of your [Tailwind configuration file](https://tailwindcss.com/docs/content-configuration). This ensures that the styling also works on production builds.
208
+
209
+ ```js
210
+ module.exports = {
211
+ content: [
212
+ './node_modules/@protonemedia/inertiajs-tables-laravel-query-builder/**/*.{js,vue}',
213
+ ]
214
+ }
215
+ ```
216
+
217
+ #### Table component
218
+
219
+ To use the `Table` component and all its related features, you must import the `Table` component and pass the `users` data to the component.
220
+
221
+ ```vue
222
+ <script setup>
223
+ import { Table } from "@protonemedia/inertiajs-tables-laravel-query-builder";
224
+
225
+ defineProps(["users"])
226
+ </script>
227
+
228
+ <template>
229
+ <Table :resource="users" />
230
+ </template>
231
+ ```
232
+
233
+ The `resource` property automatically detects the data and additional pagination meta data. You may also pass this manually to the component with the `data` and `meta` properties:
234
+
235
+ ```vue
236
+ <template>
237
+ <Table :data="users.data" :meta="users.meta" />
238
+ </template>
239
+ ```
240
+
241
+ If you want to manually render the table, like in v1 of this package, you may use the `head` and `body` slot. Additionally, you can still use the `meta` property to render the paginator.
242
+
243
+ ```vue
244
+ <template>
245
+ <Table :meta="users">
246
+ <template #head>
247
+ <tr>
248
+ <th>User</th>
249
+ </tr>
250
+ </template>
251
+
252
+ <template #body>
253
+ <tr
254
+ v-for="(user, key) in users.data"
255
+ :key="key"
256
+ >
257
+ <td>{{ user.name }}</td>
258
+ </tr>
259
+ </template>
260
+ </Table>
261
+ </template>
262
+ ```
263
+
264
+ The `Table` has some additional properties to tweak its front-end behaviour.
265
+
266
+ ```vue
267
+ <template>
268
+ <Table
269
+ :striped="true"
270
+ :prevent-overlapping-requests="false"
271
+ :input-debounce-ms="1000"
272
+ :prevent-scroll="true"
273
+ :active-classes="{text: 'text-red-500', border: 'border-red-300'}"
274
+ />
275
+ </template>
276
+ ```
277
+
278
+ | Property | Description | Default |
279
+ |----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------|
280
+ | striped | Adds a *striped* layout to the table. | `false` |
281
+ | preventOverlappingRequests | Cancels a previous visit on new user input to prevent an inconsistent state. | `true` |
282
+ | inputDebounceMs | Number of ms to wait before refreshing the table on user input. | 350 |
283
+ | preventScroll | Configures the [Scroll preservation](https://inertiajs.com/scroll-management#scroll-preservation) behavior. You may also pass `table-top` to this property to scroll to the top of the table on new data. | false |
284
+ | activeClasses | Configures the CSS classes to apply on active elements like filters & column buttons and sorting indicator | {text: 'text-green-400', border: 'border-green-300' } |
285
+
286
+ #### Custom column cells
287
+
288
+ When using *auto-fill*, you may want to transform the presented data for a specific column while leaving the other columns untouched. For this, you may use a cell template. This example is taken from the [Example Controller](#example-controller) above.
289
+
290
+ ```vue
291
+ <template>
292
+ <Table :resource="users">
293
+ <template #cell(actions)="{ item: user }">
294
+ <a :href="`/users/${user.id}/edit`">
295
+ Edit
296
+ </a>
297
+ </template>
298
+ </Table>
299
+ </template>
300
+ ```
301
+
302
+ #### Multiple tables per page
303
+
304
+ You may want to use more than one table component per page. Displaying the data is easy, but using features like filtering, sorting, and pagination requires a slightly different setup. For example, by default, the `page` query key is used for paginating the data set, but now you want two different keys for each table. Luckily, this package takes care of that and even provides a helper method to support Spatie's query package. To get this to work, you need to *name* your tables.
305
+
306
+ Let's take a look at Spatie's `QueryBuilder`. In this example, there's a table for the companies and a table for the users. We name the tables accordingly. So first, call the static `updateQueryBuilderParameters` method to tell the package to use a different set of query parameters. Now, `filter` becomes `companies_filter`, `column` becomes `companies_column`, and so forth. Secondly, change the `pageName` of the database paginator.
307
+
308
+ ```php
309
+ InertiaTable::updateQueryBuilderParameters('companies');
310
+
311
+ $companies = QueryBuilder::for(Company::query())
312
+ ->defaultSort('name')
313
+ ->allowedSorts(['name', 'email'])
314
+ ->allowedFilters(['name', 'email'])
315
+ ->paginate(pageName: 'companiesPage')
316
+ ->withQueryString();
317
+
318
+ InertiaTable::updateQueryBuilderParameters('users');
319
+
320
+ $users = QueryBuilder::for(User::query())
321
+ ->defaultSort('name')
322
+ ->allowedSorts(['name', 'email'])
323
+ ->allowedFilters(['name', 'email'])
324
+ ->paginate(pageName: 'usersPage')
325
+ ->withQueryString();
326
+ ```
327
+
328
+ Then, we need to apply these two changes to the `InertiaTable` class. There's a `name` and `pageName` method to do so.
329
+
330
+ ```php
331
+ return Inertia::render('TwoTables', [
332
+ 'companies' => $companies,
333
+ 'users' => $users,
334
+ ])->table(function (InertiaTable $inertiaTable) {
335
+ $inertiaTable
336
+ ->name('users')
337
+ ->pageName('usersPage')
338
+ ->defaultSort('name')
339
+ ->column(key: 'name', searchable: true)
340
+ ->column(key: 'email', searchable: true);
341
+ })->table(function (InertiaTable $inertiaTable) {
342
+ $inertiaTable
343
+ ->name('companies')
344
+ ->pageName('companiesPage')
345
+ ->defaultSort('name')
346
+ ->column(key: 'name', searchable: true)
347
+ ->column(key: 'address', searchable: true);
348
+ });
349
+ ```
350
+
351
+ Lastly, pass the correct `name` property to each table in the Vue template. Optionally, you may set the `preserve-scroll` property to `table-top`. This makes sure to scroll to the top of the table on new data. For example, when changing the page of the *second* table, you want to scroll to the top of the table, instead of the top of the page.
352
+
353
+ ```vue
354
+ <script setup>
355
+ import { Table } from "@protonemedia/inertiajs-tables-laravel-query-builder";
356
+
357
+ defineProps(["companies", "users"])
358
+ </script>
359
+
360
+ <template>
361
+ <Table
362
+ :resource="companies"
363
+ name="companies"
364
+ preserve-scroll="table-top"
365
+ />
366
+
367
+ <Table
368
+ :resource="users"
369
+ name="users"
370
+ preserve-scroll="table-top"
371
+ />
372
+ </template>
373
+ ```
374
+
375
+ #### Pagination translations
376
+
377
+ You can override the default pagination translations with the `setTranslations` method. You can do this in your main JavaScript file:
378
+
379
+ ```js
380
+ import { setTranslations } from "@protonemedia/inertiajs-tables-laravel-query-builder";
381
+
382
+ setTranslations({
383
+ next: "Next",
384
+ no_results_found: "No results found",
385
+ of: "of",
386
+ per_page: "per page",
387
+ previous: "Previous",
388
+ results: "results",
389
+ to: "to"
390
+ });
391
+ ```
392
+
393
+ #### Table.vue slots
394
+
395
+ The `Table.vue` has several slots that you can use to inject your own implementations.
396
+
397
+ | Slot | Description |
398
+ | --- | --- |
399
+ | tableFilter | The location of the button + dropdown to select filters. |
400
+ | tableGlobalSearch | The location of the input element that handles the global search. |
401
+ | tableReset | The location of the button that resets the table. |
402
+ | tableAddSearchRow | The location of the button + dropdown to add additional search rows. |
403
+ | tableColumns | The location of the button + dropdown to toggle columns. |
404
+ | tableSearchRows | The location of the input elements that handle the additional search rows. |
405
+ | tableWrapper | The component that *wraps* the table element, handling overflow, shadow, padding, etc. |
406
+ | table | The actual table element. |
407
+ | head | The location of the table header. |
408
+ | body | The location of the table body. |
409
+ | pagination | The location of the paginator. |
410
+
411
+ Each slot is provided with props to interact with the parent `Table` component.
412
+
413
+ ```vue
414
+ <template>
415
+ <Table>
416
+ <template v-slot:tableGlobalSearch="slotProps">
417
+ <input
418
+ placeholder="Custom Global Search Component..."
419
+ @input="slotProps.onChange($event.target.value)"
420
+ />
421
+ </template>
422
+ </Table>
423
+ </template>
424
+ ```
425
+
426
+ ## Testing
427
+
428
+ A huge [Laravel Dusk](https://laravel.com/docs/9.x/dusk) E2E test-suite can be found in the `app` directory. Here you'll find a Laravel + Inertia application.
429
+
430
+ ```bash
431
+ cd app
432
+ cp .env.example .env
433
+ composer install
434
+ npm install
435
+ npm run production
436
+ touch database/database.sqlite
437
+ php artisan migrate:fresh --seed
438
+ php artisan dusk:chrome-driver
439
+ php artisan serve
440
+ php artisan dusk
441
+ ```
442
+
443
+ ## Upgrading from v1
444
+
445
+ ### Server-side
446
+
447
+ * The `addColumn` method has been renamed to `column`.
448
+ * The `addFilter` method has been renamed to `selectFilter`.
449
+ * The `addSearch` method has been renamed to `searchInput`.
450
+ * For all renamed methods, check out the arguments as some have been changed.
451
+ * The `addColumns` and `addSearchRows` methods have been removed.
452
+ * Global Search is not enabled by default anymore.
453
+
454
+ ### Client-side
455
+
456
+ * The `InteractsWithQueryBuilder` mixin has been removed and is no longer needed.
457
+ * The `Table` component no longer needs the `filters`, `search`, `columns`, and `on-update` properties.
458
+ * When using a custom `thead` or `tbody` slot, you need to provide [the styling](https://github.com/protonemedia/inertiajs-tables-laravel-query-builder/blob/c8e21649ad372d309eeb62a8f771aa4c7cd0089e/js/Tailwind2/Table.vue#L1) manually.
459
+ * When using a custom `thead`, the `showColumn` method has been renamed to `show`.
460
+ * The `setTranslations` method is no longer part of the `Pagination` component, but should be imported.
461
+ * The templates and logic of the components are not separated anymore. Use slots to inject your own implementations.
462
+
463
+ ## v2.1 Roadmap
464
+
465
+ * Boolean filters
466
+ * Date filters
467
+ * Date range filters
468
+ * Switch to Vite for the demo app
469
+
470
+ ## Changelog
471
+
472
+ Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.
473
+
474
+ ## Contributing
475
+
476
+ Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
477
+
478
+ ## Other Laravel packages
479
+
480
+ * [`Laravel Analytics Event Tracking`](https://github.com/protonemedia/laravel-analytics-event-tracking): Laravel package to easily send events to Google Analytics.
481
+ * [`Laravel Blade On Demand`](https://github.com/protonemedia/laravel-blade-on-demand): Laravel package to compile Blade templates in memory.
482
+ * [`Laravel Cross Eloquent Search`](https://github.com/protonemedia/laravel-cross-eloquent-search): Laravel package to search through multiple Eloquent models.
483
+ * [`Laravel Eloquent Scope as Select`](https://github.com/protonemedia/laravel-eloquent-scope-as-select): Stop duplicating your Eloquent query scopes and constraints in PHP. This package lets you re-use your query scopes and constraints by adding them as a subquery.
484
+ * [`Laravel Eloquent Where Not`](https://github.com/protonemedia/laravel-eloquent-where-not): This Laravel package allows you to flip/invert an Eloquent scope, or really any query constraint.
485
+ * [`Laravel FFMpeg`](https://github.com/protonemedia/laravel-ffmpeg): This package provides an integration with FFmpeg for Laravel. The storage of the files is handled by Laravel's Filesystem.
486
+ * [`Laravel Form Components`](https://github.com/protonemedia/laravel-form-components): Blade components to rapidly build forms with Tailwind CSS Custom Forms and Bootstrap 4. Supports validation, model binding, default values, translations, includes default vendor styling and fully customizable!
487
+ * [`Laravel Mixins`](https://github.com/protonemedia/laravel-mixins): A collection of Laravel goodies.
488
+ * [`Laravel Verify New Email`](https://github.com/protonemedia/laravel-verify-new-email): This package adds support for verifying new email addresses: when a user updates its email address, it won't replace the old one until the new one is verified.
489
+ * [`Laravel Paddle`](https://github.com/protonemedia/laravel-paddle): Paddle.com API integration for Laravel with support for webhooks/events.
490
+ * [`Laravel WebDAV`](https://github.com/protonemedia/laravel-webdav): WebDAV driver for Laravel's Filesystem.
491
+
492
+ ## Security
493
+
494
+ If you discover any security related issues, please email pascal@protone.media instead of using the issue tracker.
495
+
496
+ ## Credits
497
+
498
+ - [Pascal Baljet](https://github.com/protonemedia)
499
+ - [All Contributors](../../contributors)
500
+
501
+ ## License
502
+
503
+ The MIT License (MIT). Please see [License File](LICENSE.md) for more information.