diff --git a/.all-contributorsrc b/.all-contributorsrc
index 133e8849d..41e7063e3 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -2952,6 +2952,15 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "cram42",
+ "name": "Grant Le Roux",
+ "avatar_url": "https://avatars.githubusercontent.com/u/5396871?v=4",
+ "profile": "https://github.com/cram42",
+ "contributions": [
+ "code"
+ ]
}
]
}
diff --git a/README.md b/README.md
index 19310b976..fe57aa2f3 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
 [](https://crowdin.com/project/snipe-it) [](https://hub.docker.com/r/snipe/snipe-it/) [](https://twitter.com/snipeitapp) [](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&utm_medium=referral&utm_content=snipe/snipe-it&utm_campaign=Badge_Grade)
-[](#contributors) [](https://discord.gg/yZFtShAcKk) [](https://huntr.dev)
+[](#contributors) [](https://discord.gg/yZFtShAcKk) [](https://huntr.dev)
## Snipe-IT - Open Source Asset Management System
@@ -145,7 +145,7 @@ Thanks goes to all of these wonderful people ([emoji key](https://github.com/ken
| [
Chris Hartjes](http://www.littlehart.net/atthekeyboard)
[💻](https://github.com/snipe/snipe-it/commits?author=chartjes "Code") | [
geo-chen](https://github.com/geo-chen)
[💻](https://github.com/snipe/snipe-it/commits?author=geo-chen "Code") | [
Phan Nguyen](https://github.com/nh314)
[💻](https://github.com/snipe/snipe-it/commits?author=nh314 "Code") | [
Iisakki Jaakkola](https://github.com/StarlessNights)
[💻](https://github.com/snipe/snipe-it/commits?author=StarlessNights "Code") | [
Ikko Ashimine](https://bandism.net/)
[💻](https://github.com/snipe/snipe-it/commits?author=eltociear "Code") | [
Lukas Fehling](https://github.com/lukasfehling)
[💻](https://github.com/snipe/snipe-it/commits?author=lukasfehling "Code") | [
Fernando Almeida](https://github.com/fernando-almeida)
[💻](https://github.com/snipe/snipe-it/commits?author=fernando-almeida "Code") |
| [
akemidx](https://github.com/akemidx)
[💻](https://github.com/snipe/snipe-it/commits?author=akemidx "Code") | [
Oguz Bilgic](http://oguz.site)
[💻](https://github.com/snipe/snipe-it/commits?author=oguzbilgic "Code") | [
Scooter Crawford](https://github.com/scoo73r)
[💻](https://github.com/snipe/snipe-it/commits?author=scoo73r "Code") | [
subdriven](https://github.com/subdriven)
[💻](https://github.com/snipe/snipe-it/commits?author=subdriven "Code") | [
Andrew Savinykh](https://github.com/AndrewSav)
[💻](https://github.com/snipe/snipe-it/commits?author=AndrewSav "Code") | [
Tadayuki Onishi](https://kenchan0130.github.io)
[💻](https://github.com/snipe/snipe-it/commits?author=kenchan0130 "Code") | [
Florian](https://github.com/floschoepfer)
[💻](https://github.com/snipe/snipe-it/commits?author=floschoepfer "Code") |
| [
Spencer Long](http://spencerlong.com)
[💻](https://github.com/snipe/snipe-it/commits?author=spencerrlongg "Code") | [
Marcus Moore](https://github.com/marcusmoore)
[💻](https://github.com/snipe/snipe-it/commits?author=marcusmoore "Code") | [
Martin Meredith](https://github.com/Mezzle)
| [
dboth](http://dboth.de)
[💻](https://github.com/snipe/snipe-it/commits?author=dboth "Code") | [
Zachary Fleck](https://github.com/zacharyfleck)
[💻](https://github.com/snipe/snipe-it/commits?author=zacharyfleck "Code") | [
VIKAAS-A](https://github.com/vikaas-cyper)
[💻](https://github.com/snipe/snipe-it/commits?author=vikaas-cyper "Code") | [
Abdul Kareem](https://github.com/ak-piracha)
[💻](https://github.com/snipe/snipe-it/commits?author=ak-piracha "Code") |
-| [
NojoudAlshehri](https://github.com/NojoudAlshehri)
[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") | [
Stefan Stidl](https://github.com/stefanstidlffg)
[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [
Quentin Aymard](https://github.com/qay21)
[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") |
+| [
NojoudAlshehri](https://github.com/NojoudAlshehri)
[💻](https://github.com/snipe/snipe-it/commits?author=NojoudAlshehri "Code") | [
Stefan Stidl](https://github.com/stefanstidlffg)
[💻](https://github.com/snipe/snipe-it/commits?author=stefanstidlffg "Code") | [
Quentin Aymard](https://github.com/qay21)
[💻](https://github.com/snipe/snipe-it/commits?author=qay21 "Code") | [
Grant Le Roux](https://github.com/cram42)
[💻](https://github.com/snipe/snipe-it/commits?author=cram42 "Code") |
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
diff --git a/app/Helpers/Helper.php b/app/Helpers/Helper.php
index f0f2f5e13..680c7acfd 100644
--- a/app/Helpers/Helper.php
+++ b/app/Helpers/Helper.php
@@ -1220,10 +1220,60 @@ class Helper
return true;
\Log::debug('app locked!');
}
-
+
return false;
}
+
+ /**
+ * Conversion between units of measurement
+ *
+ * @author Grant Le Roux
+ * @since 5.0
+ * @param float $value Measurement value to convert
+ * @param string $srcUnit Source unit of measurement
+ * @param string $dstUnit Destination unit of measurement
+ * @param int $round Round the result to decimals (Default false - No rounding)
+ * @return float
+ */
+ public static function convertUnit($value, $srcUnit, $dstUnit, $round=false) {
+ $srcFactor = static::getUnitConversionFactor($srcUnit);
+ $dstFactor = static::getUnitConversionFactor($dstUnit);
+ $output = $value * $srcFactor / $dstFactor;
+ return ($round !== false) ? round($output, $round) : $output;
+ }
+
+ /**
+ * Get conversion factor from unit of measurement to mm
+ *
+ * @author Grant Le Roux
+ * @since 5.0
+ * @param string $unit Unit of measurement
+ * @return float
+ */
+ public static function getUnitConversionFactor($unit) {
+ switch (strtolower($unit)) {
+ case 'mm':
+ return 1.0;
+ case 'cm':
+ return 10.0;
+ case 'm':
+ return 1000.0;
+ case 'in':
+ return 25.4;
+ case 'ft':
+ return 12 * static::getUnitConversionFactor('in');
+ case 'yd':
+ return 3 * static::getUnitConversionFactor('ft');
+ case 'pt':
+ return (1 / 72) * static::getUnitConversionFactor('in');
+ default:
+ throw new \InvalidArgumentException('Unit: \'' . $unit . '\' is not supported');
+
+ return false;
+ }
+ }
+
/*
* I know it's gauche to return a shitty HTML string, but this is just a helper and since it will be the same every single time,
diff --git a/app/Http/Controllers/Api/LabelsController.php b/app/Http/Controllers/Api/LabelsController.php
new file mode 100644
index 000000000..6576ec037
--- /dev/null
+++ b/app/Http/Controllers/Api/LabelsController.php
@@ -0,0 +1,71 @@
+
+ * @return JsonResponse
+ */
+ public function index(Request $request)
+ {
+ $this->authorize('view', Label::class);
+
+ $labels = Label::find();
+
+ if ($request->filled('search')) {
+ $search = $request->get('search');
+ $labels = $labels->filter(function ($label, $index) use ($search) {
+ return stripos($label->getName(), $search) !== false;
+ });
+ }
+
+ $total = $labels->count();
+
+ $offset = $request->get('offset', 0);
+ $offset = ($offset > $total) ? $total : $offset;
+
+ $maxLimit = config('app.max_results');
+ $limit = $request->get('limit', $maxLimit);
+ $limit = ($limit > $maxLimit) ? $maxLimit : $limit;
+
+ $labels = $labels->skip($offset)->take($limit);
+
+ return (new LabelsTransformer)->transformLabels($labels, $total, $request);
+ }
+
+ /**
+ * Returns JSON with information about a label for detail view.
+ *
+ * @author Grant Le Roux
+ * @param string $labelName
+ * @return JsonResponse
+ */
+ public function show(string $labelName)
+ {
+ $labelName = str_replace('/', '\\', $labelName);
+ try {
+ $label = Label::find($labelName);
+ } catch(ItemNotFoundException $e) {
+ return response()
+ ->json(
+ Helper::formatStandardApiResponse('error', null, trans('admin/labels/message.does_not_exist')),
+ 404
+ );
+ }
+ $this->authorize('view', $label);
+ return (new LabelsTransformer)->transformLabel($label);
+ }
+
+}
diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php
index 74b3dffa1..e699779a3 100755
--- a/app/Http/Controllers/Assets/AssetsController.php
+++ b/app/Http/Controllers/Assets/AssetsController.php
@@ -14,6 +14,7 @@ use App\Models\Location;
use App\Models\Setting;
use App\Models\Statuslabel;
use App\Models\User;
+use App\View\Label;
use Auth;
use Carbon\Carbon;
use DB;
@@ -453,11 +454,12 @@ class AssetsController extends Controller
* @since [v3.0]
* @return Redirect
*/
- public function getAssetByTag(Request $request)
+ public function getAssetByTag(Request $request, $tag=null)
{
+ $tag = $tag ? $tag : $request->get('assetTag');
$topsearch = ($request->get('topsearch') == 'true');
- if (! $asset = Asset::where('asset_tag', '=', $request->get('assetTag'))->first()) {
+ if (! $asset = Asset::where('asset_tag', '=', $tag)->first()) {
return redirect()->route('hardware.index')->with('error', trans('admin/hardware/message.does_not_exist'));
}
$this->authorize('view', $asset);
@@ -554,9 +556,11 @@ class AssetsController extends Controller
$asset = Asset::find($assetId);
$this->authorize('view', $asset);
- return view('hardware/labels')
- ->with('assets', Asset::find($asset))
+ return (new Label())
+ ->with('assets', collect([ $asset ]))
->with('settings', Setting::getSettings())
+ ->with('template', request()->get('template'))
+ ->with('offset', request()->get('offset'))
->with('bulkedit', false)
->with('count', 0);
}
diff --git a/app/Http/Controllers/Assets/BulkAssetsController.php b/app/Http/Controllers/Assets/BulkAssetsController.php
index 80d17f1b3..841909893 100644
--- a/app/Http/Controllers/Assets/BulkAssetsController.php
+++ b/app/Http/Controllers/Assets/BulkAssetsController.php
@@ -8,6 +8,7 @@ use App\Http\Controllers\CheckInOutRequest;
use App\Http\Controllers\Controller;
use App\Models\Asset;
use App\Models\Setting;
+use App\View\Label;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
@@ -45,7 +46,7 @@ class BulkAssetsController extends Controller
switch ($request->input('bulk_actions')) {
case 'labels':
$this->authorize('view', Asset::class);
- return view('hardware/labels')
+ return (new Label)
->with('assets', Asset::find($asset_ids))
->with('settings', Setting::getSettings())
->with('bulkedit', true)
diff --git a/app/Http/Controllers/LabelsController.php b/app/Http/Controllers/LabelsController.php
new file mode 100755
index 000000000..97608cb5e
--- /dev/null
+++ b/app/Http/Controllers/LabelsController.php
@@ -0,0 +1,77 @@
+
+ * @param string $labelName
+ * @return \Illuminate\Contracts\View\View
+ */
+ public function show(string $labelName)
+ {
+ $labelName = str_replace('/', '\\', $labelName);
+ $template = Label::find($labelName);
+
+ $exampleAsset = new Asset();
+
+ $exampleAsset->id = 999999;
+ $exampleAsset->name = 'AST-AB-CD-1234';
+ $exampleAsset->asset_tag = 'TCA-00001';
+ $exampleAsset->serial = 'SN9876543210';
+
+ $exampleAsset->company = new Company();
+ $exampleAsset->company->id = 999999;
+ $exampleAsset->company->name = 'Test Company Limited';
+ $exampleAsset->company->image = 'company-image-test.png';
+
+ $exampleAsset->assignedto = new User();
+ $exampleAsset->assignedto->id = 999999;
+ $exampleAsset->assignedto->first_name = 'Test';
+ $exampleAsset->assignedto->last_name = 'Person';
+ $exampleAsset->assignedto->username = 'Test.Person';
+ $exampleAsset->assignedto->employee_num = '0123456789';
+
+ $exampleAsset->model = new AssetModel();
+ $exampleAsset->model->id = 999999;
+ $exampleAsset->model->name = 'Test Model';
+ $exampleAsset->model->model_number = 'MDL5678';
+ $exampleAsset->model->manufacturer = new Manufacturer();
+ $exampleAsset->model->manufacturer->id = 999999;
+ $exampleAsset->model->manufacturer->name = 'Test Manufacturing Inc.';
+ $exampleAsset->model->category = new Category();
+ $exampleAsset->model->category->id = 999999;
+ $exampleAsset->model->category->name = 'Test Category';
+
+ $settings = Setting::getSettings();
+ if (request()->has('settings')) {
+ $overrides = request()->get('settings');
+ foreach ($overrides as $key => $value) {
+ $settings->$key = $value;
+ }
+ }
+
+ return (new LabelView())
+ ->with('assets', collect([$exampleAsset]))
+ ->with('settings', $settings)
+ ->with('template', $template)
+ ->with('bulkedit', false)
+ ->with('count', 0);
+
+ return redirect()->route('home')->with('error', trans('admin/labels/message.does_not_exist'));
+ }
+}
diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php
index 87fc98c19..f8c063842 100755
--- a/app/Http/Controllers/SettingsController.php
+++ b/app/Http/Controllers/SettingsController.php
@@ -827,6 +827,14 @@ class SettingsController extends Controller
if (is_null($setting = Setting::getSettings())) {
return redirect()->to('admin')->with('error', trans('admin/settings/message.update.error'));
}
+ $setting->label2_enable = $request->input('label2_enable');
+ $setting->label2_template = $request->input('label2_template');
+ $setting->label2_title = $request->input('label2_title');
+ $setting->label2_asset_logo = $request->input('label2_asset_logo');
+ $setting->label2_1d_type = $request->input('label2_1d_type');
+ $setting->label2_2d_type = $request->input('label2_2d_type');
+ $setting->label2_2d_target = $request->input('label2_2d_target');
+ $setting->label2_fields = $request->input('label2_fields');
$setting->labels_per_page = $request->input('labels_per_page');
$setting->labels_width = $request->input('labels_width');
$setting->labels_height = $request->input('labels_height');
diff --git a/app/Http/Transformers/LabelsTransformer.php b/app/Http/Transformers/LabelsTransformer.php
new file mode 100644
index 000000000..8e0e8ca44
--- /dev/null
+++ b/app/Http/Transformers/LabelsTransformer.php
@@ -0,0 +1,71 @@
+transformDatatables($array, $total);
+ }
+
+ public function transformLabel(Label $label)
+ {
+ $array = [
+ 'name' => $label->getName(),
+ 'unit' => $label->getUnit(),
+
+ 'width' => $label->getWidth(),
+ 'height' => $label->getHeight(),
+
+ 'margin_top' => $label->getMarginTop(),
+ 'margin_bottom' => $label->getMarginBottom(),
+ 'margin_left' => $label->getMarginLeft(),
+ 'margin_right' => $label->getMarginRight(),
+
+ 'support_asset_tag' => $label->getSupportAssetTag(),
+ 'support_1d_barcode' => $label->getSupport1DBarcode(),
+ 'support_2d_barcode' => $label->getSupport2DBarcode(),
+ 'support_fields' => $label->getSupportFields(),
+ 'support_logo' => $label->getSupportLogo(),
+ 'support_title' => $label->getSupportTitle(),
+ ];
+
+ if ($label instanceof Sheet) {
+ $array['sheet_info'] = [
+ 'label_width' => $label->getLabelWidth(),
+ 'label_height' => $label->getLabelHeight(),
+
+ 'label_margin_top' => $label->getLabelMarginTop(),
+ 'label_margin_bottom' => $label->getLabelMarginBottom(),
+ 'label_margin_left' => $label->getLabelMarginLeft(),
+ 'label_margin_right' => $label->getLabelMarginRight(),
+
+ 'labels_per_page' => $label->getLabelsPerPage(),
+ 'label_border' => $label->getLabelBorder(),
+ ];
+ }
+
+ if ($label instanceof RectangleSheet) {
+ $array['rectanglesheet_info'] = [
+ 'columns' => $label->getColumns(),
+ 'rows' => $label->getRows(),
+ 'column_spacing' => $label->getLabelColumnSpacing(),
+ 'row_spacing' => $label->getLabelRowSpacing(),
+ ];
+ }
+
+ return $array;
+ }
+}
diff --git a/app/Models/Labels/DefaultLabel.php b/app/Models/Labels/DefaultLabel.php
new file mode 100644
index 000000000..3a7dd5af1
--- /dev/null
+++ b/app/Models/Labels/DefaultLabel.php
@@ -0,0 +1,224 @@
+textSize = Helper::convertUnit($settings->labels_fontsize, 'pt', 'in');
+
+ $this->labelWidth = $settings->labels_width;
+ $this->labelHeight = $settings->labels_height;
+
+ $this->labelSpacingH = $settings->labels_display_sgutter;
+ $this->labelSpacingV = $settings->labels_display_bgutter;
+
+ $this->pageMarginTop = $settings->labels_pmargin_top;
+ $this->pageMarginBottom = $settings->labels_pmargin_bottom;
+ $this->pageMarginLeft = $settings->labels_pmargin_left;
+ $this->pageMarginRight = $settings->labels_pmargin_right;
+
+ $this->pageWidth = $settings->labels_pagewidth;
+ $this->pageHeight = $settings->labels_pageheight;
+
+ $usableWidth = $this->pageWidth - $this->pageMarginLeft - $this->pageMarginRight;
+ $usableHeight = $this->pageHeight - $this->pageMarginTop - $this->pageMarginBottom;
+
+ $this->columns = ($usableWidth + $this->labelSpacingH) / ($this->labelWidth + $this->labelSpacingH);
+ $this->rows = ($usableHeight + $this->labelSpacingV) / ($this->labelHeight + $this->labelSpacingV);
+ }
+
+ public function getUnit() { return 'in'; }
+
+ public function getPageWidth() { return $this->pageWidth; }
+ public function getPageHeight() { return $this->pageHeight; }
+
+ public function getPageMarginTop() { return $this->pageMarginTop; }
+ public function getPageMarginBottom() { return $this->pageMarginBottom; }
+ public function getPageMarginLeft() { return $this->pageMarginLeft; }
+ public function getPageMarginRight() { return $this->pageMarginRight; }
+
+ public function getColumns() { return $this->columns; }
+ public function getRows() { return $this->rows; }
+ public function getLabelBorder() { return 0.01; }
+
+ public function getLabelWidth() { return $this->labelWidth; }
+ public function getLabelHeight() { return $this->labelHeight; }
+
+ public function getLabelMarginTop() { return 0; }
+ public function getLabelMarginBottom() { return 0; }
+ public function getLabelMarginLeft() { return 0; }
+ public function getLabelMarginRight() { return 0; }
+
+ public function getLabelColumnSpacing() { return $this->labelSpacingH; }
+ public function getLabelRowSpacing() { return $this->labelSpacingV; }
+
+ public function getSupportAssetTag() { return false; }
+ public function getSupport1DBarcode() { return true; }
+ public function getSupport2DBarcode() { return true; }
+ public function getSupportFields() { return 4; }
+ public function getSupportTitle() { return true; }
+ public function getSupportLogo() { return true; }
+
+ public function preparePDF($pdf) {}
+
+ public function write($pdf, $record) {
+
+ $asset = $record->get('asset');
+ $settings = Setting::getSettings();
+
+ $textY = 0;
+ $textX1 = 0;
+ $textX2 = $this->getLabelWidth();
+
+ // 1D Barcode
+ if ($record->get('barcode1d')) {
+ static::write1DBarcode(
+ $pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type,
+ 0.05, $this->getLabelHeight() - self::BARCODE1D_SIZE,
+ $this->getLabelWidth() - 0.1, self::BARCODE1D_SIZE
+ );
+ }
+
+ // 2D Barcode
+ if ($record->get('barcode2d')) {
+ static::write2DBarcode(
+ $pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
+ 0, 0, self::BARCODE2D_SIZE, self::BARCODE2D_SIZE
+ );
+ $textX1 += self::BARCODE2D_SIZE + self::BARCODE2D_MARGIN;
+ }
+
+ // Logo
+ if ($record->get('logo')) {
+ $logoSize = static::writeImage(
+ $pdf, $record->get('logo'),
+ $this->labelWidth - self::LOGO_SIZE[0], 0,
+ self::LOGO_SIZE[0], self::LOGO_SIZE[1],
+ 'R', 'T', 300, true, false, 0
+ );
+ $textX2 -= ($logoSize[0] + self::LOGO_MARGIN);
+ }
+
+ $textW = $textX2 - $textX1;
+
+ // Title
+ if ($record->get('title')) {
+ static::writeText(
+ $pdf, $record->get('title'),
+ $textX1, 0,
+ 'freesans', 'b', $this->textSize, 'L',
+ $textW, $this->textSize,
+ true, 0
+ );
+ $textY += $this->textSize + self::TEXT_MARGIN;
+ }
+
+ // Fields
+ $fieldsDone = 0;
+ if ($settings->labels_display_name && $fieldsDone < $this->getSupportFields()) {
+ if ($asset->name) {
+ static::writeText(
+ $pdf, 'N: '.$asset->name,
+ $textX1, $textY,
+ 'freesans', '', $this->textSize, 'L',
+ $textW, $this->textSize,
+ true, 0
+ );
+ $textY += $this->textSize + self::TEXT_MARGIN;
+ $fieldsDone++;
+ }
+ }
+ if ($settings->labels_display_company_name && $fieldsDone < $this->getSupportFields()) {
+ if ($asset->company) {
+ static::writeText(
+ $pdf, 'C: '.$asset->company->name,
+ $textX1, $textY,
+ 'freesans', '', $this->textSize, 'L',
+ $textW, $this->textSize,
+ true, 0
+ );
+ $textY += $this->textSize + self::TEXT_MARGIN;
+ $fieldsDone++;
+ }
+ }
+ if ($settings->labels_display_tag && $fieldsDone < $this->getSupportFields()) {
+ if ($asset->asset_tag) {
+ static::writeText(
+ $pdf, 'T: '.$asset->asset_tag,
+ $textX1, $textY,
+ 'freesans', '', $this->textSize, 'L',
+ $textW, $this->textSize,
+ true, 0
+ );
+ $textY += $this->textSize + self::TEXT_MARGIN;
+ $fieldsDone++;
+ }
+ }
+ if ($settings->labels_display_serial && $fieldsDone < $this->getSupportFields()) {
+ if ($asset->serial) {
+ static::writeText(
+ $pdf, 'S: '.$asset->serial,
+ $textX1, $textY,
+ 'freesans', '', $this->textSize, 'L',
+ $textW, $this->textSize,
+ true, 0
+ );
+ $textY += $this->textSize + self::TEXT_MARGIN;
+ $fieldsDone++;
+ }
+ }
+ if ($settings->labels_display_model && $fieldsDone < $this->getSupportFields()) {
+ if ($asset->model) {
+ static::writeText(
+ $pdf, 'M: '.$asset->model->name,
+ $textX1, $textY,
+ 'freesans', '', $this->textSize, 'L',
+ $textW, $this->textSize,
+ true, 0
+ );
+ $textY += $this->textSize + self::TEXT_MARGIN;
+ $fieldsDone++;
+ }
+ }
+
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/app/Models/Labels/Field.php b/app/Models/Labels/Field.php
new file mode 100644
index 000000000..b264c7ac2
--- /dev/null
+++ b/app/Models/Labels/Field.php
@@ -0,0 +1,39 @@
+options; }
+ public function setOptions($options) {
+ $tempCollect = collect($options);
+ if (!$tempCollect->contains(fn($o) => !is_subclass_of($o, FieldOption::class))) {
+ $this->options = $options;
+ }
+ }
+
+ public function toArray(Asset $asset) { return Field::makeArray($this, $asset); }
+
+ /* Statics */
+
+ public static function makeArray(Field $field, Asset $asset) {
+ return $field->getOptions()
+ ->map(fn($option) => $option->toArray($asset))
+ ->filter(fn($result) => $result['value'] != null);
+ }
+
+ public static function makeString(Field $option) {
+ return implode('|', $option->getOptions());
+ }
+
+ public static function fromString(string $theString) {
+ $field = new Field();
+ $field->options = collect(explode('|', $theString))
+ ->filter(fn($optionString) => !empty($optionString))
+ ->map(fn($optionString) => FieldOption::fromString($optionString));
+ return $field;
+ }
+}
\ No newline at end of file
diff --git a/app/Models/Labels/FieldOption.php b/app/Models/Labels/FieldOption.php
new file mode 100644
index 000000000..76427acca
--- /dev/null
+++ b/app/Models/Labels/FieldOption.php
@@ -0,0 +1,49 @@
+label; }
+
+ protected string $dataSource;
+ public function getDataSource() { return $this->dataSource; }
+
+ public function getValue(Asset $asset) {
+ $dataPath = collect(explode('.', $this->dataSource));
+ return $dataPath->reduce(function ($myValue, $path) {
+ try { return $myValue ? $myValue->{$path} : ${$myValue}; }
+ catch (\Exception $e) { return $myValue; }
+ }, $asset);
+ }
+
+ public function toArray(Asset $asset=null) { return FieldOption::makeArray($this, $asset); }
+ public function toString() { return FieldOption::makeString($this); }
+
+ /* Statics */
+
+ public static function makeArray(FieldOption $option, Asset $asset=null) {
+ return [
+ 'label' => $option->getLabel(),
+ 'dataSource' => $option->getDataSource(),
+ 'value' => $asset ? $option->getValue($asset) : null
+ ];
+ }
+
+ public static function makeString(FieldOption $option) {
+ return $option->getLabel() . '=' . $option->getDataSource();
+ }
+
+ public static function fromString(string $theString) {
+ $parts = explode('=', $theString);
+ if (count($parts) == 2) {
+ $option = new FieldOption();
+ $option->label = $parts[0];
+ $option->dataSource = $parts[1];
+ return $option;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/Models/Labels/Label.php b/app/Models/Labels/Label.php
new file mode 100644
index 000000000..ff759ac54
--- /dev/null
+++ b/app/Models/Labels/Label.php
@@ -0,0 +1,598 @@
+each(function ($record, $index) use ($pdf) {
+ $pdf->AddPage();
+ $this->write($pdf, $record);
+ });
+ }
+
+ /**
+ * Returns the qualified class name relative to the Label class's namespace.
+ *
+ * @return string
+ */
+ public final function getName() {
+ $refClass = new \ReflectionClass(Label::class);
+ return str_replace($refClass->getNamespaceName() . '\\', '', get_class($this));
+ }
+
+ /**
+ * Returns the label's orientation as a string.
+ * 'L' = Landscape
+ * 'P' = Portrait
+ *
+ * @return string
+ */
+ public final function getOrientation() {
+ return ($this->getWidth() >= $this->getHeight()) ? 'L' : 'P';
+ }
+
+ /**
+ * Returns the label's printable area as an object.
+ *
+ * @return object [ 'x1'=>0.00, 'y1'=>0.00, 'x2'=>0.00, 'y2'=>0.00, 'w'=>0.00, 'h'=>0.00 ]
+ */
+ public final function getPrintableArea() {
+ return (object)[
+ 'x1' => $this->getMarginLeft(),
+ 'y1' => $this->getMarginTop(),
+ 'x2' => $this->getWidth() - $this->getMarginRight(),
+ 'y2' => $this->getHeight() - $this->getMarginBottom(),
+ 'w' => $this->getWidth() - $this->getMarginLeft() - $this->getMarginRight(),
+ 'h' => $this->getHeight() - $this->getMarginTop() - $this->getMarginBottom(),
+ ];
+ }
+
+ /**
+ * Write a text cell.
+ *
+ * @param TCPDF $pdf The TCPDF instance
+ * @param string $text The text to write. Supports 'some **bold** text'.
+ * @param float $x X position of top-left
+ * @param float $y Y position of top-left
+ * @param string $font The font family
+ * @param string $style The font style
+ * @param int $size The font size in getUnit() units
+ * @param string $align Align text in the box. 'L' left, 'R' right, 'C' center.
+ * @param float $width Force text box width. NULL to auto-fit.
+ * @param float $height Force text box height. NULL to auto-fit.
+ * @param bool $squash Squash text if it's too big
+ * @param int $border Thickness of border. Default = 0.
+ * @param int $spacing Letter spacing. Default = 0.
+ */
+ public final function writeText(TCPDF $pdf, $text, $x, $y, $font=null, $style=null, $size=null, $align='L', $width=null, $height=null, $squash=false, $border=0, $spacing=0) {
+ $prevFamily = $pdf->getFontFamily();
+ $prevStyle = $pdf->getFontStyle();
+ $prevSizePt = $pdf->getFontSizePt();
+
+ $text = !empty($text) ? $text : '';
+
+ $fontFamily = !empty($font) ? $font : $prevFamily;
+ $fontStyle = !empty($style) ? $style : $prevStyle;
+ if ($size) $fontSizePt = Helper::convertUnit($size, $this->getUnit(), 'pt', true);
+ else $fontSizePt = $prevSizePt;
+
+ $pdf->SetFontSpacing($spacing);
+
+ $parts = collect(explode('**', $text))
+ ->map(function ($part, $index) use ($pdf, $fontFamily, $fontStyle, $fontSizePt) {
+ $modStyle = ($index % 2 == 1) ? 'B' : $fontStyle;
+ $pdf->setFont($fontFamily, $modStyle, $fontSizePt);
+ return [
+ 'text' => $part,
+ 'text_width' => $pdf->GetStringWidth($part),
+ 'font_family' => $fontFamily,
+ 'font_style' => $modStyle,
+ 'font_size' => $fontSizePt,
+ ];
+ });
+
+ $textWidth = $parts->reduce(function ($carry, $part) { return $carry += $part['text_width']; });
+ $cellWidth = !empty($width) ? $width : $textWidth;
+
+ if ($squash && ($textWidth > 0)) {
+ $scaleFactor = min(1.0, $cellWidth / $textWidth);
+ $parts = $parts->map(function ($part, $index) use ($scaleFactor) {
+ $part['text_width'] = $part['text_width'] * $scaleFactor;
+ return $part;
+ });
+ }
+ $cellHeight = !empty($height) ? $height : Helper::convertUnit($fontSizePt, 'pt', $this->getUnit());
+
+ if ($border) {
+ $prevLineWidth = $pdf->getLineWidth();
+ $pdf->setLineWidth($border);
+ $pdf->Rect($x, $y, $cellWidth, $cellHeight);
+ $pdf->setLineWidth($prevLineWidth);
+ }
+
+ switch($align) {
+ case 'R': $startX = ($x + $cellWidth) - min($cellWidth, $textWidth); break;
+ case 'C': $startX = ($x + ($cellWidth / 2)) - (min($cellWidth, $textWidth) / 2); break;
+ case 'L':
+ default: $startX = $x; break;
+ }
+
+ $parts->reduce(function ($currentX, $part) use ($pdf, $y, $cellHeight) {
+ $pdf->SetXY($currentX, $y);
+ $pdf->setFont($part['font_family'], $part['font_style'], $part['font_size']);
+ $pdf->Cell($part['text_width'], $cellHeight, $part['text'], 0, 0, '', false, '', 1, true);
+ return $currentX += $part['text_width'];
+ }, $startX);
+
+ $pdf->SetFont($prevFamily, $prevStyle, $prevSizePt);
+ $pdf->SetFontSpacing(0);
+ }
+
+ /**
+ * Write an image.
+ *
+ * @param TCPDF $pdf The TCPDF instance
+ * @param string $image The image to write
+ * @param float $x X position of top-left
+ * @param float $y Y position of top-left
+ * @param float $width The container width
+ * @param float $height The container height
+ * @param string $halign Align text in the box. 'L' left, 'R' right, 'C' center. Default 'L'.
+ * @param string $valign Align text in the box. 'T' top, 'B' bottom, 'C' center. Default 'T'.
+ * @param int $dpi Pixels per inch
+ * @param bool $resize Resize to fit container
+ * @param bool $stretch Stretch (vs Scale) to fit container
+ * @param int $border Thickness of border. Default = 0.
+ *
+ * @return array Returns the final calculated size [w,h]
+ */
+ public final function writeImage(TCPDF $pdf, $image, $x, $y, $width=null, $height=null, $halign='L', $valign='L', $dpi=300, $resize=false, $stretch=false, $border=0) {
+
+ if (empty($image)) return [0,0];
+
+ $imageInfo = getimagesize($image);
+ if (!$imageInfo) return [0,0]; // TODO: SVG or other
+
+ $imageWidthPx = $imageInfo[0];
+ $imageHeightPx = $imageInfo[1];
+ $imageType = image_type_to_extension($imageInfo[2], false);
+
+ $imageRatio = $imageWidthPx / $imageHeightPx;
+ $dpu = Helper::convertUnit($dpi, $this->getUnit(), 'in');
+ $imageWidth = $imageWidthPx / $dpu;
+ $imageHeight = $imageHeightPx / $dpu;
+
+ $outputWidth = $imageWidth;
+ $outputHeight = $imageHeight;
+
+ if ($resize) {
+ // Assign specified parameters
+ $limitWidth = $width;
+ $limitHeight = $height;
+
+ // If not, try calculating from the other dimension
+ $limitWidth = ($limitWidth > 0) ? $limitWidth : ($limitHeight / $imageRatio);
+ $limitHeight = ($limitHeight > 0) ? $limitHeight : ($limitWidth * $imageRatio);
+
+ // If not, just use the image size
+ $limitWidth = ($limitWidth > 0) ? $limitWidth : $imageWidth;
+ $limitHeight = ($limitHeight > 0) ? $limitHeight : $imageHeight;
+
+ $scaleWidth = $limitWidth / $imageWidth;
+ $scaleHeight = $limitHeight / $imageHeight;
+
+ // If non-stretch, make both scales factors equal
+ if (!$stretch) {
+ // Do we need to scale down at all? That's most important.
+ if (($scaleWidth < 1.0) || ($scaleHeight < 1.0)) {
+ // Choose largest scale-down
+ $scaleWidth = min($scaleWidth, $scaleHeight);
+ } else {
+ // Choose smallest scale-up
+ $scaleWidth = min(max($scaleWidth, 1.0), max($scaleHeight, 1.0));
+ }
+ $scaleHeight = $scaleWidth;
+ }
+
+ $outputWidth = $imageWidth * $scaleWidth;
+ $outputHeight = $imageHeight * $scaleHeight;
+ }
+
+ // Container
+ $containerWidth = !empty($width) ? $width : $outputWidth;
+ $containerHeight = !empty($height) ? $height : $outputHeight;
+
+ // Horizontal Position
+ switch ($halign) {
+ case 'R': $originX = ($x + $containerWidth) - $outputWidth; break;
+ case 'C': $originX = ($x + ($containerWidth / 2)) - ($outputWidth / 2); break;
+ case 'L':
+ default: $originX = $x; break;
+ }
+
+ // Vertical Position
+ switch ($valign) {
+ case 'B': $originY = ($y + $containerHeight) - $outputHeight; break;
+ case 'C': $originY = ($y + ($containerHeight / 2)) - ($outputHeight / 2); break;
+ case 'T':
+ default: $originY = $y; break;
+ }
+
+ // Actual Image
+ $pdf->Image($image, $originX, $originY, $outputWidth, $outputHeight, $imageType, '', '', true);
+
+ // Border
+ if ($border) {
+ $prevLineWidth = $pdf->getLineWidth();
+ $pdf->setLineWidth($border);
+ $pdf->Rect($x, $y, $containerWidth, $containerHeight);
+ $pdf->setLineWidth($prevLineWidth);
+ }
+
+ return [ $outputWidth, $outputHeight ];
+ }
+
+ /**
+ * Write a 1D barcode.
+ *
+ * @param TCPDF $pdf The TCPDF instance
+ * @param string $value The barcode content
+ * @param string $type The barcode type
+ * @param float $x X position of top-left
+ * @param float $y Y position of top-left
+ * @param float $width The container width
+ * @param float $height The container height
+ */
+ public final function write1DBarcode(TCPDF $pdf, $value, $type, $x, $y, $width, $height) {
+ if (empty($value)) return;
+ $pdf->write1DBarcode($value, $type, $x, $y, $width, $height, null, ['stretch'=>true]);
+ }
+
+ /**
+ * Write a 2D barcode.
+ *
+ * @param TCPDF $pdf The TCPDF instance
+ * @param string $value The barcode content
+ * @param string $type The barcode type
+ * @param float $x X position of top-left
+ * @param float $y Y position of top-left
+ * @param float $width The container width
+ * @param float $height The container height
+ */
+ public final function write2DBarcode(TCPDF $pdf, $value, $type, $x, $y, $width, $height) {
+ if (empty($value)) return;
+ $pdf->write2DBarcode($value, $type, $x, $y, $width, $height, null, ['stretch'=>true]);
+ }
+
+
+
+ /**
+ * Checks the template is internally valid
+ */
+ public final function validate() {
+ $this->validateUnits();
+ $this->validateSize();
+ $this->validateMargins();
+ $this->validateSupport();
+ }
+
+ private function validateUnits() {
+ $validUnits = [ 'pt', 'mm', 'cm', 'in' ];
+ $unit = $this->getUnit();
+ if (!in_array(strtolower($unit), $validUnits)) {
+ throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_value', [
+ 'name' => 'getUnit()',
+ 'expected' => '[ \''.implode('\', \'', $validUnits).'\' ]',
+ 'actual' => '\''.$unit.'\''
+ ]));
+ }
+ }
+
+ private function validateSize() {
+ $width = $this->getWidth();
+ if (!is_numeric($width) || is_string($width)) {
+ throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [
+ 'name' => 'getWidth()',
+ 'expected' => 'float',
+ 'actual' => gettype($width)
+ ]));
+ }
+
+ $height = $this->getHeight();
+ if (!is_numeric($height) || is_string($height)) {
+ throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [
+ 'name' => 'getHeight()',
+ 'expected' => 'float',
+ 'actual' => gettype($height)
+ ]));
+ }
+ }
+
+ private function validateMargins() {
+ $marginTop = $this->getMarginTop();
+ if (!is_numeric($marginTop) || is_string($marginTop)) {
+ throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [
+ 'name' => 'getMarginTop()',
+ 'expected' => 'float',
+ 'actual' => gettype($marginTop)
+ ]));
+ }
+
+ $marginBottom = $this->getMarginBottom();
+ if (!is_numeric($marginBottom) || is_string($marginBottom)) {
+ throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [
+ 'name' => 'getMarginBottom()',
+ 'expected' => 'float',
+ 'actual' => gettype($marginBottom)
+ ]));
+ }
+
+ $marginLeft = $this->getMarginLeft();
+ if (!is_numeric($marginLeft) || is_string($marginLeft)) {
+ throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [
+ 'name' => 'getMarginLeft()',
+ 'expected' => 'float',
+ 'actual' => gettype($marginLeft)
+ ]));
+ }
+
+ $marginRight = $this->getMarginRight();
+ if (!is_numeric($marginRight) || is_string($marginRight)) {
+ throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [
+ 'name' => 'getMarginRight()',
+ 'expected' => 'float',
+ 'actual' => gettype($marginRight)
+ ]));
+ }
+ }
+
+ private function validateSupport() {
+ $support1D = $this->getSupport1DBarcode();
+ if (!is_bool($support1D)) {
+ throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [
+ 'name' => 'getSupport1DBarcode()',
+ 'expected' => 'boolean',
+ 'actual' => gettype($support1D)
+ ]));
+ }
+
+ $support2D = $this->getSupport2DBarcode();
+ if (!is_bool($support2D)) {
+ throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [
+ 'name' => 'getSupport2DBarcode()',
+ 'expected' => 'boolean',
+ 'actual' => gettype($support2D)
+ ]));
+ }
+
+ $supportFields = $this->getSupportFields();
+ if (!is_int($supportFields)) {
+ throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [
+ 'name' => 'getSupportFields()',
+ 'expected' => 'integer',
+ 'actual' => gettype($supportFields)
+ ]));
+ }
+
+ $supportLogo = $this->getSupportLogo();
+ if (!is_bool($supportLogo)) {
+ throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [
+ 'name' => 'getSupportLogo()',
+ 'expected' => 'boolean',
+ 'actual' => gettype($supportLogo)
+ ]));
+ }
+
+ $supportTitle = $this->getSupportTitle();
+ if (!is_bool($supportTitle)) {
+ throw new \UnexpectedValueException(trans('admin/labels/message.invalid_return_type', [
+ 'name' => 'getSupportTitle()',
+ 'expected' => 'boolean',
+ 'actual' => gettype($supportTitle)
+ ]));
+ }
+ }
+
+
+
+ /**
+ * Public Static Functions
+ */
+
+ /**
+ * Find size of a page by its format.
+ *
+ * @param string $format Format name (eg: 'A4', 'LETTER', etc.)
+ * @param string $orientation 'L' for Landscape, 'P' for Portrait ('L' default)
+ * @param string $unit Unit of measure to return in ('mm' default)
+ *
+ * @return object (object)[ 'width' => (float)123.4, 'height' => (float)123.4 ]
+ */
+ public static function fromFormat($format, $orientation='L', $unit='mm', $round=false) {
+ $size = collect(TCPDF_STATIC::getPageSizeFromFormat(strtoupper($format)))
+ ->sort()
+ ->map(function ($value) use ($unit) {
+ return Helper::convertUnit($value, 'pt', $unit);
+ })
+ ->toArray();
+ $width = ($orientation == 'L') ? $size[1] : $size[0];
+ $height = ($orientation == 'L') ? $size[0] : $size[1];
+ return (object)[
+ 'width' => ($round !== false) ? round($width, $round) : $width,
+ 'height' => ($round !== false) ? round($height, $round) : $height,
+ ];
+ }
+
+ /**
+ * Find a Label by its path (or just return them all).
+ *
+ * Unlike most Models, these are defined by their existence as non-
+ * abstract classes stored in Models\Labels.
+ *
+ * @param string|Arrayable|array|null $path Label path[s]
+ * @return Collection|Label|null
+ */
+ public static function find($name=null) {
+ // Find many
+ if (is_array($name) || $name instanceof Arrayable) {
+ $labels = collect($name)
+ ->map(function ($thisname) {
+ return static::find($thisname);
+ })
+ ->whereNotNull();
+ return ($labels->count() > 0) ? $labels : null;
+ }
+
+ // Find one
+ if ($name !== null) {
+ return static::find()
+ ->sole(function ($label) use ($name) {
+ return $label->getName() == $name;
+ });
+ }
+
+ // Find all
+ return collect(File::allFiles(__DIR__))
+ ->map(function ($file) {
+ preg_match_all('/\/*(.+?)(?:\/|\.)/', $file->getRelativePathName(), $matches);
+ return __NAMESPACE__ . '\\' . implode('\\', $matches[1]);
+ })
+ ->filter(function ($name) {
+ if (!class_exists($name)) return false;
+ $refClass = new \ReflectionClass($name);
+ if ($refClass->isAbstract()) return false;
+ return $refClass->isSubclassOf(Label::class);
+ })
+ ->map(function ($name) {
+ return new $name();
+ });
+ }
+
+
+
+}
diff --git a/app/Models/Labels/RectangleSheet.php b/app/Models/Labels/RectangleSheet.php
new file mode 100644
index 000000000..f5fe5cda9
--- /dev/null
+++ b/app/Models/Labels/RectangleSheet.php
@@ -0,0 +1,48 @@
+getColumns() * $this->getRows(); }
+
+ public function getLabelPosition($index) {
+ $printIndex = $index + $this->getLabelIndexOffset();
+ $row = (int)($printIndex / $this->getColumns());
+ $col = $printIndex - ($row * $this->getColumns());
+ $x = $this->getPageMarginLeft() + (($this->getLabelWidth() + $this->getLabelColumnSpacing()) * $col);
+ $y = $this->getPageMarginTop() + (($this->getLabelHeight() + $this->getLabelRowSpacing()) * $row);
+ return [ $x, $y ];
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/app/Models/Labels/Sheet.php b/app/Models/Labels/Sheet.php
new file mode 100644
index 000000000..83e363591
--- /dev/null
+++ b/app/Models/Labels/Sheet.php
@@ -0,0 +1,209 @@
+getPageWidth(); }
+ public function getHeight() { return $this->getPageHeight(); }
+ public function getMarginTop() { return $this->getPageMarginTop(); }
+ public function getMarginBottom() { return $this->getPageMarginBottom(); }
+ public function getMarginLeft() { return $this->getPageMarginLeft(); }
+ public function getMarginRight() { return $this->getPageMarginRight(); }
+
+ /**
+ * Returns the page width in getUnit() units
+ *
+ * @return float
+ */
+ public abstract function getPageWidth();
+
+ /**
+ * Returns the page height in getUnit() units
+ *
+ * @return float
+ */
+ public abstract function getPageHeight();
+
+ /**
+ * Returns the page top margin in getUnit() units
+ *
+ * @return float
+ */
+ public abstract function getPageMarginTop();
+
+ /**
+ * Returns the page bottom margin in getUnit() units
+ *
+ * @return float
+ */
+ public abstract function getPageMarginBottom();
+
+ /**
+ * Returns the page left margin in getUnit() units
+ *
+ * @return float
+ */
+ public abstract function getPageMarginLeft();
+
+ /**
+ * Returns the page right margin in getUnit() units
+ *
+ * @return float
+ */
+ public abstract function getPageMarginRight();
+
+ /**
+ * Returns the page width in getUnit() units
+ *
+ * @return float
+ */
+ public abstract function getLabelWidth();
+
+ /**
+ * Returns each label's height in getUnit() units
+ *
+ * @return float
+ */
+ public abstract function getLabelHeight();
+
+ /**
+ * Returns each label's top margin in getUnit() units
+ *
+ * @return float
+ */
+ public abstract function getLabelMarginTop();
+
+ /**
+ * Returns each label's bottom margin in getUnit() units
+ *
+ * @return float
+ */
+ public abstract function getLabelMarginBottom();
+
+ /**
+ * Returns each label's left margin in getUnit() units
+ *
+ * @return float
+ */
+ public abstract function getLabelMarginLeft();
+
+ /**
+ * Returns each label's right margin in getUnit() units
+ *
+ * @return float
+ */
+ public abstract function getLabelMarginRight();
+
+ /**
+ * Returns the number of labels each page supports
+ *
+ * @return int
+ */
+ public abstract function getLabelsPerPage();
+
+ /**
+ * Returns label position based on its index
+ *
+ * @param int $index
+ *
+ * @return array [x,y]
+ */
+ public abstract function getLabelPosition(int $index);
+
+ /**
+ * Returns the border to draw around labels
+ *
+ * @return int
+ */
+ public abstract function getLabelBorder();
+
+ /**
+ * Handle the data here. Override for multiple-per-page handling
+ *
+ * @param TCPDF $pdf The TCPDF instance
+ * @param Collection $data The data
+ */
+ public function writeAll($pdf, $data) {
+ $prevPageNumber = -1;
+
+ foreach ($data->toArray() as $recordIndex => $record) {
+
+ $pageNumber = (int)($recordIndex / $this->getLabelsPerPage());
+ if ($pageNumber != $prevPageNumber) {
+ $pdf->AddPage();
+ $prevPageNumber = $pageNumber;
+ }
+
+ $pageIndex = $recordIndex - ($this->getLabelsPerPage() * $pageNumber);
+ $position = $this->getLabelPosition($pageIndex);
+
+ $pdf->StartTemplate();
+ $this->write($pdf, $data->get($recordIndex));
+ $template = $pdf->EndTemplate();
+
+ $pdf->printTemplate($template, $position[0], $position[1]);
+
+ if ($this->getLabelBorder()) {
+ $prevLineWidth = $pdf->GetLineWidth();
+
+ $borderThickness = $this->getLabelBorder();
+ $borderOffset = $borderThickness / 2;
+ $borderX = $position[0]- $borderOffset;
+ $borderY = $position[1] - $borderOffset;
+ $borderW = $this->getLabelWidth() + $borderThickness;
+ $borderH = $this->getLabelHeight() + $borderThickness;
+
+ $pdf->setLineWidth($borderThickness);
+ $pdf->Rect($borderX, $borderY, $borderW, $borderH);
+ $pdf->setLineWidth($prevLineWidth);
+ }
+ }
+ }
+
+ /**
+ * Returns each label's orientation as a string.
+ * 'L' = Landscape
+ * 'P' = Portrait
+ *
+ * @return string
+ */
+ public final function getLabelOrientation() {
+ return ($this->getLabelWidth() >= $this->getLabelHeight()) ? 'L' : 'P';
+ }
+
+ /**
+ * Returns each label's printable area as an object.
+ *
+ * @return object [ 'x1'=>0.00, 'y1'=>0.00, 'x2'=>0.00, 'y2'=>0.00, 'w'=>0.00, 'h'=>0.00 ]
+ */
+ public final function getLabelPrintableArea() {
+ return (object)[
+ 'x1' => $this->getLabelMarginLeft(),
+ 'y1' => $this->getLabelMarginTop(),
+ 'x2' => $this->getLabelWidth() - $this->getLabelMarginRight(),
+ 'y2' => $this->getLabelHeight() - $this->getLabelMarginBottom(),
+ 'w' => $this->getLabelWidth() - $this->getLabelMarginLeft() - $this->getLabelMarginRight(),
+ 'h' => $this->getLabelHeight() - $this->getLabelMarginTop() - $this->getLabelMarginBottom(),
+ ];
+ }
+
+ /**
+ * Returns label index offset (skip positions)
+ *
+ * @return int
+ */
+ public function getLabelIndexOffset() { return $this->indexOffset; }
+
+ /**
+ * Sets label index offset (skip positions)
+ *
+ * @param int $offset
+ *
+ */
+ public function setLabelIndexOffset(int $offset) { $this->indexOffset = $offset; }
+}
+
+?>
\ No newline at end of file
diff --git a/app/Models/Labels/Sheets/Avery/L7162.php b/app/Models/Labels/Sheets/Avery/L7162.php
new file mode 100644
index 000000000..e1097db9b
--- /dev/null
+++ b/app/Models/Labels/Sheets/Avery/L7162.php
@@ -0,0 +1,71 @@
+getUnit(), 0);
+ $this->pageWidth = $paperSize->width;
+ $this->pageHeight = $paperSize->height;
+
+ $this->pageMarginLeft = Helper::convertUnit(self::COLUMN1_X, 'pt', $this->getUnit());
+ $this->pageMarginTop = Helper::convertUnit(self::ROW1_Y, 'pt', $this->getUnit());
+
+ $columnSpacingPt = self::COLUMN2_X - self::COLUMN1_X - self::LABEL_W;
+ $this->columnSpacing = Helper::convertUnit($columnSpacingPt, 'pt', $this->getUnit());
+ $rowSpacingPt = self::ROW2_Y - self::ROW1_Y - self::LABEL_H;
+ $this->rowSpacing = Helper::convertUnit($rowSpacingPt, 'pt', $this->getUnit());
+
+ $this->labelWidth = Helper::convertUnit(self::LABEL_W, 'pt', $this->getUnit());
+ $this->labelHeight = Helper::convertUnit(self::LABEL_H, 'pt', $this->getUnit());
+ }
+
+ public function getPageWidth() { return $this->pageWidth; }
+ public function getPageHeight() { return $this->pageHeight; }
+
+ public function getPageMarginTop() { return $this->pageMarginTop; }
+ public function getPageMarginBottom() { return $this->pageMarginTop; }
+ public function getPageMarginLeft() { return $this->pageMarginLeft; }
+ public function getPageMarginRight() { return $this->pageMarginLeft; }
+
+ public function getColumns() { return 2; }
+ public function getRows() { return 8; }
+
+ public function getLabelColumnSpacing() { return $this->columnSpacing; }
+ public function getLabelRowSpacing() { return $this->rowSpacing; }
+
+ public function getLabelWidth() { return $this->labelWidth; }
+ public function getLabelHeight() { return $this->labelHeight; }
+
+ public function getLabelBorder() { return 0; }
+}
+
+?>
\ No newline at end of file
diff --git a/app/Models/Labels/Sheets/Avery/L7162_A.php b/app/Models/Labels/Sheets/Avery/L7162_A.php
new file mode 100644
index 000000000..0b3312ba7
--- /dev/null
+++ b/app/Models/Labels/Sheets/Avery/L7162_A.php
@@ -0,0 +1,100 @@
+getLabelPrintableArea();
+
+ $usableWidth = $pa->w;
+ $usableHeight = $pa->h;
+ $currentX = $pa->x1;
+ $currentY = $pa->y1;
+ $titleShiftX = 0;
+
+ $barcodeSize = $pa->h - self::TAG_SIZE;
+
+ if ($record->has('barcode2d')) {
+ static::writeText(
+ $pdf, $record->get('tag'),
+ $pa->x1, $pa->y2 - self::TAG_SIZE,
+ 'freemono', 'b', self::TAG_SIZE, 'C',
+ $barcodeSize, self::TAG_SIZE, true, 0
+ );
+ static::write2DBarcode(
+ $pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
+ $pa->x1, $pa->y1,
+ $barcodeSize, $barcodeSize
+ );
+ $currentX += $barcodeSize + self::BARCODE_MARGIN;
+ $usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
+ } else {
+ static::writeText(
+ $pdf, $record->get('tag'),
+ $pa->x1, $pa->y1,
+ 'freemono', 'b', self::TITLE_SIZE, 'L',
+ $barcodeSize, self::TITLE_SIZE, true, 0
+ );
+ $titleShiftX = $barcodeSize;
+ }
+
+ if ($record->has('title')) {
+ static::writeText(
+ $pdf, $record->get('title'),
+ $currentX + $titleShiftX, $currentY,
+ 'freesans', '', self::TITLE_SIZE, 'L',
+ $usableWidth, self::TITLE_SIZE, true, 0
+ );
+ $currentY += self::TITLE_SIZE + self::TITLE_MARGIN;
+ }
+
+ foreach ($record->get('fields') as $field) {
+ static::writeText(
+ $pdf, $field['label'],
+ $currentX, $currentY,
+ 'freesans', '', self::LABEL_SIZE, 'L',
+ $usableWidth, self::LABEL_SIZE, true, 0
+ );
+ $currentY += self::LABEL_SIZE + self::LABEL_MARGIN;
+
+ static::writeText(
+ $pdf, $field['value'],
+ $currentX, $currentY,
+ 'freemono', 'B', self::FIELD_SIZE, 'L',
+ $usableWidth, self::FIELD_SIZE, true, 0, 0.3
+ );
+ $currentY += self::FIELD_SIZE + self::FIELD_MARGIN;
+ }
+
+ }
+}
+
+
+?>
\ No newline at end of file
diff --git a/app/Models/Labels/Sheets/Avery/L7162_B.php b/app/Models/Labels/Sheets/Avery/L7162_B.php
new file mode 100644
index 000000000..268754e04
--- /dev/null
+++ b/app/Models/Labels/Sheets/Avery/L7162_B.php
@@ -0,0 +1,103 @@
+getLabelPrintableArea();
+
+ $usableWidth = $pa->w;
+ $usableHeight = $pa->h;
+ $currentX = $pa->x1;
+ $currentY = $pa->y1;
+
+ if ($record->has('barcode1d')) {
+ static::write1DBarcode(
+ $pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type,
+ $pa->x1, $pa->y2 - self::BARCODE_SIZE,
+ $usableWidth, self::BARCODE_SIZE
+ );
+ $usableHeight -= self::BARCODE_SIZE + self::BARCODE_MARGIN;
+ }
+
+ if ($record->has('logo')) {
+ $logoSize = static::writeImage(
+ $pdf, $record->get('logo'),
+ $pa->x1, $pa->y1,
+ self::LOGO_MAX_WIDTH, $usableHeight,
+ 'L', 'T', 300, true, false, 0.1
+ );
+ $currentX += $logoSize[0] + self::LOGO_MARGIN;
+ $usableWidth -= $logoSize[0] + self::LOGO_MARGIN;
+ }
+
+ if ($record->has('title')) {
+ static::writeText(
+ $pdf, $record->get('title'),
+ $currentX, $currentY,
+ 'freesans', '', self::TITLE_SIZE, 'L',
+ $usableWidth, self::TITLE_SIZE, true, 0
+ );
+ $currentY += self::TITLE_SIZE + self::TITLE_MARGIN;
+ }
+
+ foreach ($record->get('fields') as $field) {
+ static::writeText(
+ $pdf, $field['label'],
+ $currentX, $currentY,
+ 'freesans', '', self::LABEL_SIZE, 'L',
+ $usableWidth, self::LABEL_SIZE, true, 0
+ );
+ $currentY += self::LABEL_SIZE + self::LABEL_MARGIN;
+
+ static::writeText(
+ $pdf, $field['value'],
+ $currentX, $currentY,
+ 'freemono', 'B', self::FIELD_SIZE, 'L',
+ $usableWidth, self::FIELD_SIZE, true, 0, 0.3
+ );
+ $currentY += self::FIELD_SIZE + self::FIELD_MARGIN;
+ }
+
+ static::writeText(
+ $pdf, $record->get('tag'),
+ $currentX, $pa->y2 - self::BARCODE_SIZE - self::BARCODE_MARGIN - self::TAG_SIZE,
+ 'freemono', 'b', self::TAG_SIZE, 'R',
+ $usableWidth, self::TAG_SIZE, true, 0, 0.3
+ );
+
+ }
+}
+
+
+?>
\ No newline at end of file
diff --git a/app/Models/Labels/Sheets/Avery/L7163.php b/app/Models/Labels/Sheets/Avery/L7163.php
new file mode 100644
index 000000000..f14326033
--- /dev/null
+++ b/app/Models/Labels/Sheets/Avery/L7163.php
@@ -0,0 +1,71 @@
+getUnit(), 0);
+ $this->pageWidth = $paperSize->width;
+ $this->pageHeight = $paperSize->height;
+
+ $this->pageMarginLeft = Helper::convertUnit(self::COLUMN1_X, 'pt', $this->getUnit());
+ $this->pageMarginTop = Helper::convertUnit(self::ROW1_Y, 'pt', $this->getUnit());
+
+ $columnSpacingPt = self::COLUMN2_X - self::COLUMN1_X - self::LABEL_W;
+ $this->columnSpacing = Helper::convertUnit($columnSpacingPt, 'pt', $this->getUnit());
+ $rowSpacingPt = self::ROW2_Y - self::ROW1_Y - self::LABEL_H;
+ $this->rowSpacing = Helper::convertUnit($rowSpacingPt, 'pt', $this->getUnit());
+
+ $this->labelWidth = Helper::convertUnit(self::LABEL_W, 'pt', $this->getUnit());
+ $this->labelHeight = Helper::convertUnit(self::LABEL_H, 'pt', $this->getUnit());
+ }
+
+ public function getPageWidth() { return $this->pageWidth; }
+ public function getPageHeight() { return $this->pageHeight; }
+
+ public function getPageMarginTop() { return $this->pageMarginTop; }
+ public function getPageMarginBottom() { return $this->pageMarginTop; }
+ public function getPageMarginLeft() { return $this->pageMarginLeft; }
+ public function getPageMarginRight() { return $this->pageMarginLeft; }
+
+ public function getColumns() { return 2; }
+ public function getRows() { return 7; }
+
+ public function getLabelColumnSpacing() { return $this->columnSpacing; }
+ public function getLabelRowSpacing() { return $this->rowSpacing; }
+
+ public function getLabelWidth() { return $this->labelWidth; }
+ public function getLabelHeight() { return $this->labelHeight; }
+
+ public function getLabelBorder() { return 0; }
+}
+
+?>
\ No newline at end of file
diff --git a/app/Models/Labels/Sheets/Avery/L7163_A.php b/app/Models/Labels/Sheets/Avery/L7163_A.php
new file mode 100644
index 000000000..6dc33f64d
--- /dev/null
+++ b/app/Models/Labels/Sheets/Avery/L7163_A.php
@@ -0,0 +1,98 @@
+getLabelPrintableArea();
+
+ $usableWidth = $pa->w;
+ $usableHeight = $pa->h;
+ $currentX = $pa->x1;
+ $currentY = $pa->y1;
+
+ if ($record->has('title')) {
+ static::writeText(
+ $pdf, $record->get('title'),
+ $currentX, $currentY,
+ 'freesans', '', self::TITLE_SIZE, 'C',
+ $usableWidth, self::TITLE_SIZE, true, 0
+ );
+ $currentY += self::TITLE_SIZE + self::TITLE_MARGIN;
+ }
+
+ $barcodeSize = $pa->h - self::TITLE_SIZE - self::TITLE_MARGIN - self::TAG_SIZE;
+
+ if ($record->has('barcode2d')) {
+ static::writeText(
+ $pdf, $record->get('tag'),
+ $pa->x1, $pa->y2 - self::TAG_SIZE,
+ 'freemono', 'b', self::TAG_SIZE, 'C',
+ $barcodeSize, self::TAG_SIZE, true, 0
+ );
+ static::write2DBarcode(
+ $pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
+ $pa->x1, $currentY,
+ $barcodeSize, $barcodeSize
+ );
+ $currentX += $barcodeSize + self::BARCODE_MARGIN;
+ $usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
+ } else {
+ static::writeText(
+ $pdf, $record->get('tag'),
+ $pa->x1, $pa->y2 - self::TAG_SIZE,
+ 'freemono', 'b', self::TAG_SIZE, 'R',
+ $usableWidth, self::TAG_SIZE, true, 0
+ );
+ }
+
+ foreach ($record->get('fields') as $field) {
+ static::writeText(
+ $pdf, $field['label'],
+ $currentX, $currentY,
+ 'freesans', '', self::LABEL_SIZE, 'L',
+ $usableWidth, self::LABEL_SIZE, true, 0
+ );
+ $currentY += self::LABEL_SIZE + self::LABEL_MARGIN;
+
+ static::writeText(
+ $pdf, $field['value'],
+ $currentX, $currentY,
+ 'freemono', 'B', self::FIELD_SIZE, 'L',
+ $usableWidth, self::FIELD_SIZE, true, 0, 0.5
+ );
+ $currentY += self::FIELD_SIZE + self::FIELD_MARGIN;
+ }
+
+ }
+}
+
+
+?>
\ No newline at end of file
diff --git a/app/Models/Labels/Sheets/Avery/_5267.php b/app/Models/Labels/Sheets/Avery/_5267.php
new file mode 100644
index 000000000..f5f2f1355
--- /dev/null
+++ b/app/Models/Labels/Sheets/Avery/_5267.php
@@ -0,0 +1,71 @@
+getUnit(), 2);
+ $this->pageWidth = $paperSize->width;
+ $this->pageHeight = $paperSize->height;
+
+ $this->pageMarginLeft = Helper::convertUnit(self::COLUMN1_X, 'pt', $this->getUnit());
+ $this->pageMarginTop = Helper::convertUnit(self::ROW1_Y, 'pt', $this->getUnit());
+
+ $columnSpacingPt = self::COLUMN2_X - self::COLUMN1_X - self::LABEL_W;
+ $this->columnSpacing = Helper::convertUnit($columnSpacingPt, 'pt', $this->getUnit());
+ $rowSpacingPt = self::ROW2_Y - self::ROW1_Y - self::LABEL_H;
+ $this->rowSpacing = Helper::convertUnit($rowSpacingPt, 'pt', $this->getUnit());
+
+ $this->labelWidth = Helper::convertUnit(self::LABEL_W, 'pt', $this->getUnit());
+ $this->labelHeight = Helper::convertUnit(self::LABEL_H, 'pt', $this->getUnit());
+ }
+
+ public function getPageWidth() { return $this->pageWidth; }
+ public function getPageHeight() { return $this->pageHeight; }
+
+ public function getPageMarginTop() { return $this->pageMarginTop; }
+ public function getPageMarginBottom() { return $this->pageMarginTop; }
+ public function getPageMarginLeft() { return $this->pageMarginLeft; }
+ public function getPageMarginRight() { return $this->pageMarginLeft; }
+
+ public function getColumns() { return 4; }
+ public function getRows() { return 20; }
+
+ public function getLabelColumnSpacing() { return $this->columnSpacing; }
+ public function getLabelRowSpacing() { return $this->rowSpacing; }
+
+ public function getLabelWidth() { return $this->labelWidth; }
+ public function getLabelHeight() { return $this->labelHeight; }
+
+ public function getLabelBorder() { return 0; }
+}
+
+?>
\ No newline at end of file
diff --git a/app/Models/Labels/Sheets/Avery/_5267_A.php b/app/Models/Labels/Sheets/Avery/_5267_A.php
new file mode 100644
index 000000000..efe0855d5
--- /dev/null
+++ b/app/Models/Labels/Sheets/Avery/_5267_A.php
@@ -0,0 +1,68 @@
+getLabelPrintableArea();
+
+ if ($record->has('barcode1d')) {
+ static::write1DBarcode(
+ $pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type,
+ $pa->x1, $pa->y2 - self::BARCODE_SIZE,
+ $pa->w, self::BARCODE_SIZE
+ );
+ }
+
+ if ($record->has('title')) {
+ static::writeText(
+ $pdf, $record->get('title'),
+ $pa->x1, $pa->y1,
+ 'freesans', '', self::TITLE_SIZE, 'L',
+ $pa->w, self::TITLE_SIZE, true, 0
+ );
+ }
+
+ $fieldY = $pa->y2 - self::BARCODE_SIZE - self::BARCODE_MARGIN - self::FIELD_SIZE;
+ if ($record->has('fields')) {
+ if ($record->get('fields')->count() >= 1) {
+ $field = $record->get('fields')->first();
+ static::writeText(
+ $pdf, $field['value'],
+ $pa->x1, $fieldY,
+ 'freemono', 'B', self::FIELD_SIZE, 'C',
+ $pa->w, self::FIELD_SIZE, true, 0, 0.01
+ );
+ }
+ }
+
+ }
+}
+
+
+?>
\ No newline at end of file
diff --git a/app/Models/Labels/Sheets/Avery/_5520.php b/app/Models/Labels/Sheets/Avery/_5520.php
new file mode 100644
index 000000000..00cb0e068
--- /dev/null
+++ b/app/Models/Labels/Sheets/Avery/_5520.php
@@ -0,0 +1,71 @@
+getUnit(), 2);
+ $this->pageWidth = $paperSize->width;
+ $this->pageHeight = $paperSize->height;
+
+ $this->pageMarginLeft = Helper::convertUnit(self::COLUMN1_X, 'pt', $this->getUnit());
+ $this->pageMarginTop = Helper::convertUnit(self::ROW1_Y, 'pt', $this->getUnit());
+
+ $columnSpacingPt = self::COLUMN2_X - self::COLUMN1_X - self::LABEL_W;
+ $this->columnSpacing = Helper::convertUnit($columnSpacingPt, 'pt', $this->getUnit());
+ $rowSpacingPt = self::ROW2_Y - self::ROW1_Y - self::LABEL_H;
+ $this->rowSpacing = Helper::convertUnit($rowSpacingPt, 'pt', $this->getUnit());
+
+ $this->labelWidth = Helper::convertUnit(self::LABEL_W, 'pt', $this->getUnit());
+ $this->labelHeight = Helper::convertUnit(self::LABEL_H, 'pt', $this->getUnit());
+ }
+
+ public function getPageWidth() { return $this->pageWidth; }
+ public function getPageHeight() { return $this->pageHeight; }
+
+ public function getPageMarginTop() { return $this->pageMarginTop; }
+ public function getPageMarginBottom() { return $this->pageMarginTop; }
+ public function getPageMarginLeft() { return $this->pageMarginLeft; }
+ public function getPageMarginRight() { return $this->pageMarginLeft; }
+
+ public function getColumns() { return 3; }
+ public function getRows() { return 10; }
+
+ public function getLabelColumnSpacing() { return $this->columnSpacing; }
+ public function getLabelRowSpacing() { return $this->rowSpacing; }
+
+ public function getLabelWidth() { return $this->labelWidth; }
+ public function getLabelHeight() { return $this->labelHeight; }
+
+ public function getLabelBorder() { return 0; }
+}
+
+?>
\ No newline at end of file
diff --git a/app/Models/Labels/Sheets/Avery/_5520_A.php b/app/Models/Labels/Sheets/Avery/_5520_A.php
new file mode 100644
index 000000000..199566d24
--- /dev/null
+++ b/app/Models/Labels/Sheets/Avery/_5520_A.php
@@ -0,0 +1,85 @@
+getLabelPrintableArea();
+
+ $currentX = $pa->x1;
+ $currentY = $pa->y1;
+ $usableWidth = $pa->w;
+ $usableHeight = $pa->h;
+
+ if ($record->has('title')) {
+ static::writeText(
+ $pdf, $record->get('title'),
+ $pa->x1, $pa->y1,
+ 'freesans', '', self::TITLE_SIZE, 'C',
+ $pa->w, self::TITLE_SIZE, true, 0
+ );
+ $currentY += self::TITLE_SIZE + self::TITLE_MARGIN;
+ $usableHeight -= self::TITLE_SIZE + self::TITLE_MARGIN;
+ }
+
+ $barcodeSize = $usableHeight;
+ if ($record->has('barcode2d')) {
+ static::write2DBarcode(
+ $pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
+ $currentX, $currentY,
+ $barcodeSize, $barcodeSize
+ );
+ $currentX += $barcodeSize + self::BARCODE_MARGIN;
+ $usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
+ }
+
+ foreach ($record->get('fields') as $field) {
+ static::writeText(
+ $pdf, $field['label'],
+ $currentX, $currentY,
+ 'freesans', '', self::LABEL_SIZE, 'L',
+ $usableWidth, self::LABEL_SIZE, true, 0
+ );
+ $currentY += self::LABEL_SIZE + self::LABEL_MARGIN;
+
+ static::writeText(
+ $pdf, $field['value'],
+ $currentX, $currentY,
+ 'freemono', 'B', self::FIELD_SIZE, 'L',
+ $usableWidth, self::FIELD_SIZE, true, 0, 0.01
+ );
+ $currentY += self::FIELD_SIZE + self::FIELD_MARGIN;
+ }
+
+ }
+}
+
+
+?>
\ No newline at end of file
diff --git a/app/Models/Labels/Tapes/Brother/TZe_12mm.php b/app/Models/Labels/Tapes/Brother/TZe_12mm.php
new file mode 100644
index 000000000..f9196847c
--- /dev/null
+++ b/app/Models/Labels/Tapes/Brother/TZe_12mm.php
@@ -0,0 +1,19 @@
+getUnit()); }
+ public function getMarginTop() { return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit()); }
+ public function getMarginBottom() { return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit());}
+ public function getMarginLeft() { return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); }
+ public function getMarginRight() { return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); }
+}
\ No newline at end of file
diff --git a/app/Models/Labels/Tapes/Brother/TZe_12mm_A.php b/app/Models/Labels/Tapes/Brother/TZe_12mm_A.php
new file mode 100644
index 000000000..f89cfc5d4
--- /dev/null
+++ b/app/Models/Labels/Tapes/Brother/TZe_12mm_A.php
@@ -0,0 +1,56 @@
+getPrintableArea();
+
+ if ($record->has('barcode1d')) {
+ static::write1DBarcode(
+ $pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type,
+ $pa->x1, $pa->y1, $pa->w, self::BARCODE_SIZE
+ );
+ }
+
+ $currentY = $pa->y1 + self::BARCODE_SIZE + self::BARCODE_MARGIN;
+ $usableHeight = $pa->h - self::BARCODE_SIZE - self::BARCODE_MARGIN;
+ $fontSize = $usableHeight + self::TEXT_SIZE_MOD;
+
+ $tagWidth = $pa->w / 3;
+ $fieldWidth = $pa->w / 3 * 2;
+
+ static::writeText(
+ $pdf, $record->get('tag'),
+ $pa->x1, $currentY,
+ 'freemono', 'b', $fontSize, 'L',
+ $tagWidth, $usableHeight, true, 0, 0
+ );
+
+ if ($record->get('fields')->count() >= 1) {
+ static::writeText(
+ $pdf, $record->get('fields')->values()->get(0)['value'],
+ $pa->x1 + ($tagWidth), $currentY,
+ 'freemono', 'b', $fontSize, 'R',
+ $fieldWidth, $usableHeight, true, 0, 0
+ );
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/app/Models/Labels/Tapes/Brother/TZe_24mm.php b/app/Models/Labels/Tapes/Brother/TZe_24mm.php
new file mode 100644
index 000000000..3c67bc161
--- /dev/null
+++ b/app/Models/Labels/Tapes/Brother/TZe_24mm.php
@@ -0,0 +1,19 @@
+getUnit()); }
+ public function getMarginTop() { return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit()); }
+ public function getMarginBottom() { return Helper::convertUnit(self::MARGIN_SIDES, 'mm', $this->getUnit());}
+ public function getMarginLeft() { return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); }
+ public function getMarginRight() { return Helper::convertUnit(self::MARGIN_ENDS, 'mm', $this->getUnit()); }
+}
\ No newline at end of file
diff --git a/app/Models/Labels/Tapes/Brother/TZe_24mm_A.php b/app/Models/Labels/Tapes/Brother/TZe_24mm_A.php
new file mode 100644
index 000000000..ea4c6c9df
--- /dev/null
+++ b/app/Models/Labels/Tapes/Brother/TZe_24mm_A.php
@@ -0,0 +1,87 @@
+getPrintableArea();
+
+ $currentX = $pa->x1;
+ $currentY = $pa->y1;
+ $usableWidth = $pa->w;
+
+ $barcodeSize = $pa->h - self::TAG_SIZE;
+
+ if ($record->has('barcode2d')) {
+ static::writeText(
+ $pdf, $record->get('tag'),
+ $pa->x1, $pa->y2 - self::TAG_SIZE,
+ 'freemono', 'b', self::TAG_SIZE, 'C',
+ $barcodeSize, self::TAG_SIZE, true, 0
+ );
+ static::write2DBarcode(
+ $pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type,
+ $currentX, $currentY,
+ $barcodeSize, $barcodeSize
+ );
+ $currentX += $barcodeSize + self::BARCODE_MARGIN;
+ $usableWidth -= $barcodeSize + self::BARCODE_MARGIN;
+ } else {
+ static::writeText(
+ $pdf, $record->get('tag'),
+ $pa->x1, $pa->y2 - self::TAG_SIZE,
+ 'freemono', 'b', self::TAG_SIZE, 'R',
+ $usableWidth, self::TAG_SIZE, true, 0
+ );
+ }
+
+ if ($record->has('title')) {
+ static::writeText(
+ $pdf, $record->get('title'),
+ $currentX, $currentY,
+ 'freesans', '', self::TITLE_SIZE, 'L',
+ $usableWidth, self::TITLE_SIZE, true, 0
+ );
+ $currentY += self::TITLE_SIZE + self::TITLE_MARGIN;
+ }
+
+ foreach ($record->get('fields') as $field) {
+ static::writeText(
+ $pdf, $field['label'],
+ $currentX, $currentY,
+ 'freesans', '', self::LABEL_SIZE, 'L',
+ $usableWidth, self::LABEL_SIZE, true, 0, 0
+ );
+ $currentY += self::LABEL_SIZE + self::LABEL_MARGIN;
+
+ static::writeText(
+ $pdf, $field['value'],
+ $currentX, $currentY,
+ 'freemono', 'B', self::FIELD_SIZE, 'L',
+ $usableWidth, self::FIELD_SIZE, true, 0, 0.3
+ );
+ $currentY += self::FIELD_SIZE + self::FIELD_MARGIN;
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/Presenters/LabelPresenter.php b/app/Presenters/LabelPresenter.php
new file mode 100644
index 000000000..5ff95d2c4
--- /dev/null
+++ b/app/Presenters/LabelPresenter.php
@@ -0,0 +1,96 @@
+ 'radio',
+ 'radio' => true,
+ 'formatter' => 'labelRadioFormatter'
+ ], [
+ 'field' => 'name',
+ 'searchable' => true,
+ 'sortable' => true,
+ 'switchable' => true,
+ 'title' => trans('general.name'),
+ 'visible' => true,
+ ], [
+ 'field' => 'size',
+ 'searchable' => false,
+ 'sortable' => false,
+ 'switchable' => true,
+ 'title' => trans('admin/settings/table.size'),
+ 'visible' => true,
+ 'formatter' => 'labelSizeFormatter'
+ ], [
+ 'field' => 'labels_per_page',
+ 'searchable' => false,
+ 'sortable' => false,
+ 'switchable' => true,
+ 'title' => trans('admin/labels/table.labels_per_page'),
+ 'visible' => true,
+ 'formatter' => 'labelPerPageFormatter'
+ ], [
+ 'field' => 'support_fields',
+ 'searchable' => false,
+ 'sortable' => true,
+ 'switchable' => true,
+ 'title' => trans('admin/labels/table.support_fields'),
+ 'visible' => true
+ ], [
+ 'field' => 'support_asset_tag',
+ 'searchable' => false,
+ 'sortable' => true,
+ 'switchable' => true,
+ 'title' => trans('admin/labels/table.support_asset_tag'),
+ 'visible' => true,
+ 'formatter' => 'trueFalseFormatter'
+ ], [
+ 'field' => 'support_1d_barcode',
+ 'searchable' => false,
+ 'sortable' => true,
+ 'switchable' => true,
+ 'title' => trans('admin/labels/table.support_1d_barcode'),
+ 'visible' => true,
+ 'formatter' => 'trueFalseFormatter'
+ ], [
+ 'field' => 'support_2d_barcode',
+ 'searchable' => false,
+ 'sortable' => true,
+ 'switchable' => true,
+ 'title' => trans('admin/labels/table.support_2d_barcode'),
+ 'visible' => true,
+ 'formatter' => 'trueFalseFormatter'
+ ], [
+ 'field' => 'support_logo',
+ 'searchable' => false,
+ 'sortable' => true,
+ 'switchable' => true,
+ 'title' => trans('admin/labels/table.support_logo'),
+ 'visible' => true,
+ 'formatter' => 'trueFalseFormatter'
+ ], [
+ 'field' => 'support_title',
+ 'searchable' => false,
+ 'sortable' => true,
+ 'switchable' => true,
+ 'title' => trans('admin/labels/table.support_title'),
+ 'visible' => true,
+ 'formatter' => 'trueFalseFormatter'
+ ]
+ ];
+
+ return json_encode($layout);
+ }
+}
diff --git a/app/View/Label.php b/app/View/Label.php
new file mode 100644
index 000000000..de955afff
--- /dev/null
+++ b/app/View/Label.php
@@ -0,0 +1,211 @@
+data = new Collection();
+ }
+
+ /**
+ * Render the PDF label.
+ *
+ * @param callable|null $callback
+ */
+ public function render(callable $callback = null)
+ {
+ $settings = $this->data->get('settings');
+ $assets = $this->data->get('assets');
+ $offset = $this->data->get('offset');
+ $template = $this->data->get('template');
+
+ // If disabled, pass to legacy view
+ if ((!$settings->label2_enable) && (!$template)) {
+ return view('hardware/labels')
+ ->with('assets', $assets)
+ ->with('settings', $settings)
+ ->with('bulkedit', $this->data->get('bulkedit'))
+ ->with('count', $this->data->get('count'));
+ }
+
+ if (empty($template)) $template = LabelModel::find($settings->label2_template);
+ elseif (is_string($template)) $template = LabelModel::find($template);
+
+ $template->validate();
+
+ $pdf = new TCPDF(
+ $template->getOrientation(),
+ $template->getUnit(),
+ [ $template->getWidth(), $template->getHeight() ]
+ );
+
+ // Reset parameters
+ $pdf->SetPrintHeader(false);
+ $pdf->SetPrintFooter(false);
+ $pdf->SetAutoPageBreak(false);
+ $pdf->SetMargins(0, 0, null, true);
+ $pdf->SetCellMargins(0, 0, 0, 0);
+ $pdf->SetCellPaddings(0, 0, 0, 0);
+ $pdf->setCreator('Snipe-IT');
+ $pdf->SetSubject('Asset Labels');
+ $template->preparePDF($pdf);
+
+ // Get fields from settings
+ $fieldDefinitions = collect(explode(';', $settings->label2_fields))
+ ->filter(fn($fieldString) => !empty($fieldString))
+ ->map(fn($fieldString) => Field::fromString($fieldString));
+
+ // Prepare data
+ $data = $assets
+ ->map(function ($asset) use ($template, $settings, $fieldDefinitions) {
+
+ $assetData = new Collection();
+
+ $assetData->put('asset', $asset);
+ $assetData->put('id', $asset->id);
+ $assetData->put('tag', $asset->asset_tag);
+
+ if ($template->getSupportTitle()) {
+ $title = !empty($settings->label2_title) ?
+ str_ireplace(':company', $asset->company->name, $settings->label2_title) :
+ $settings->qr_text;
+ if (!empty($title)) $assetData->put('title', $title);
+ }
+
+ if ($template->getSupportLogo()) {
+ $logo = $settings->label2_asset_logo ?
+ (
+ !empty($asset->company->image) ?
+ Storage::disk('public')->path('companies/'.e($asset->company->image)) :
+ null
+ ) :
+ (
+ !empty($settings->label_logo) ?
+ Storage::disk('public')->path(''.e($settings->label_logo)) :
+ null
+ );
+ if (!empty($logo)) $assetData->put('logo', $logo);
+ }
+
+ if ($template->getSupport1DBarcode()) {
+ $barcode1DType = $settings->label2_1d_type;
+ $barcode1DType = ($barcode1DType == 'default') ?
+ (($settings->alt_barcode_enabled) ? $settings->alt_barcode : null) :
+ $barcode1DType;
+ if ($barcode1DType != 'none') {
+ $assetData->put('barcode1d', (object)[
+ 'type' => $barcode1DType,
+ 'content' => $asset->asset_tag,
+ ]);
+ }
+ }
+
+ if ($template->getSupport2DBarcode()) {
+ $barcode2DType = $settings->label2_2d_type;
+ $barcode2DType = ($barcode2DType == 'default') ?
+ $settings->barcode_type :
+ $barcode2DType;
+ if (($barcode2DType != 'none') && (!is_null($barcode2DType))) {
+ switch ($settings->label2_2d_target) {
+ case 'ht_tag': $barcode2DTarget = route('ht/assetTag', $asset->asset_tag); break;
+ case 'hardware_id':
+ default: $barcode2DTarget = route('hardware.show', $asset->id); break;
+ }
+ $assetData->put('barcode2d', (object)[
+ 'type' => $barcode2DType,
+ 'content' => $barcode2DTarget,
+ ]);
+ }
+ }
+
+ $fields = $fieldDefinitions
+ ->map(fn($field) => $field->toArray($asset))
+ ->filter(fn($field) => $field != null)
+ ->reduce(function($myFields, $field) {
+ // Remove Duplicates
+ $toAdd = $field
+ ->filter(fn($o) => !$myFields->contains('dataSource', $o['dataSource']))
+ ->first();
+
+ return $toAdd ? $myFields->push($toAdd) : $myFields;
+ }, new Collection());
+
+ $assetData->put('fields', $fields->take($template->getSupportFields()));
+
+ return $assetData;
+ });
+
+ if ($template instanceof Sheet) {
+ $template->setLabelIndexOffset($offset ?? 0);
+ }
+ $template->writeAll($pdf, $data);
+
+ $filename = $assets->count() > 1 ? 'assets.pdf' : $assets->first()->asset_tag.'.pdf';
+ $pdf->Output($filename, 'I');
+ }
+
+ /**
+ * Add a piece of data.
+ *
+ * @param string|array $key
+ * @param mixed $value
+ * @return $this
+ */
+ public function with($key, $value = null)
+ {
+ $this->data->put($key, $value);
+ return $this;
+ }
+
+ /**
+ * Get the array of view data.
+ *
+ * @return array
+ */
+ public function getData()
+ {
+ return $this->data;
+ }
+
+ /**
+ * Get the name of the view.
+ *
+ * @return string
+ */
+ public function name()
+ {
+ return $this->getName();
+ }
+
+ /**
+ * Get the name of the view.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return self::NAME;
+ }
+
+}
\ No newline at end of file
diff --git a/composer.json b/composer.json
index 2b127f907..bb9acd9b1 100644
--- a/composer.json
+++ b/composer.json
@@ -70,6 +70,7 @@
"spatie/laravel-backup": "^6.16",
"symfony/polyfill-mbstring": "^1.22",
"tecnickcom/tc-lib-barcode": "^1.15",
+ "tecnickcom/tcpdf": "^6.5.0",
"unicodeveloper/laravel-password": "^1.0",
"watson/validating": "^6.1"
},
diff --git a/database/migrations/2022_10_25_215520_add_label2_settings.php b/database/migrations/2022_10_25_215520_add_label2_settings.php
new file mode 100644
index 000000000..692e8440d
--- /dev/null
+++ b/database/migrations/2022_10_25_215520_add_label2_settings.php
@@ -0,0 +1,50 @@
+boolean('label2_enable')->default(false);
+ $table->string('label2_template')->nullable()->default('DefaultLabel');
+ $table->string('label2_title')->nullable()->default(null);
+ $table->boolean('label2_asset_logo')->default(false);
+ $table->string('label2_1d_type')->default('default');
+ $table->string('label2_2d_type')->default('default');
+ $table->string('label2_2d_target')->default('hardware_id');
+ $table->string('label2_fields')->default(
+ trans('admin/hardware/form.name').'=name;'.
+ trans('admin/hardware/form.serial').'=serial;'.
+ trans('admin/hardware/form.model').'=model.name;'
+ );
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('settings', function (Blueprint $table) {
+ if (Schema::hasColumn('settings', 'label2_enable')) $table->dropColumn('label2_enable');
+ if (Schema::hasColumn('settings', 'label2_template')) $table->dropColumn('label2_template');
+ if (Schema::hasColumn('settings', 'label2_title')) $table->dropColumn('label2_title');
+ if (Schema::hasColumn('settings', 'label2_asset_logo')) $table->dropColumn('label2_asset_logo');
+ if (Schema::hasColumn('settings', 'label2_1d_type')) $table->dropColumn('label2_1d_type');
+ if (Schema::hasColumn('settings', 'label2_2d_type')) $table->dropColumn('label2_2d_type');
+ if (Schema::hasColumn('settings', 'label2_2d_target')) $table->dropColumn('label2_2d_target');
+ if (Schema::hasColumn('settings', 'label2_fields')) $table->dropColumn('label2_fields');
+ });
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index 649581f94..45a1132bc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1883,6 +1883,19 @@
}
}
},
+ "@vue/reactivity": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz",
+ "integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==",
+ "requires": {
+ "@vue/shared": "3.1.5"
+ }
+ },
+ "@vue/shared": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz",
+ "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA=="
+ },
"@webassemblyjs/ast": {
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz",
@@ -2187,6 +2200,14 @@
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ=="
},
+ "alpinejs": {
+ "version": "3.12.3",
+ "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.12.3.tgz",
+ "integrity": "sha512-fLz2dfYQ3xCk7Ip8LiIpV2W+9brUyex2TAE7Z0BCvZdUDklJE+n+a8gCgLWzfZ0GzZNZu7HUP8Z0z6Xbm6fsSA==",
+ "requires": {
+ "@vue/reactivity": "~3.1.1"
+ }
+ },
"ansi-html-community": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz",
diff --git a/package.json b/package.json
index 67b598854..7a497786e 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,7 @@
"acorn-import-assertions": "^1.9.0",
"admin-lte": "^2.4.18",
"ajv": "^6.12.6",
+ "alpinejs": "^3.10.5",
"blueimp-file-upload": "^9.34.0",
"bootstrap": "^3.4.1",
"bootstrap-colorpicker": "^2.5.3",
diff --git a/public/js/dist/all-defer.js b/public/js/dist/all-defer.js
new file mode 100644
index 000000000..0302f4127
--- /dev/null
+++ b/public/js/dist/all-defer.js
@@ -0,0 +1 @@
+(()=>{var e,t,n,r,i=!1,o=!1,a=[],s=-1;function l(e){!function(e){a.includes(e)||a.push(e);o||i||(i=!0,queueMicrotask(u))}(e)}function c(e){let t=a.indexOf(e);-1!==t&&t>s&&a.splice(t,1)}function u(){i=!1,o=!0;for(let e=0;e{(void 0===t||t.includes(n))&&(r.forEach((e=>e())),delete e._x_attributeCleanups[n])}))}var m=new MutationObserver(S),v=!1;function y(){m.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),v=!0}function b(){(w=w.concat(m.takeRecords())).length&&!E&&(E=!0,queueMicrotask((()=>{S(w),w.length=0,E=!1}))),m.disconnect(),v=!1}var w=[],E=!1;function A(e){if(!v)return e();b();let t=e();return y(),t}var O=!1,k=[];function S(e){if(O)return void(k=k.concat(e));let t=[],n=[],r=new Map,i=new Map;for(let o=0;o1===e.nodeType&&t.push(e))),e[o].removedNodes.forEach((e=>1===e.nodeType&&n.push(e)))),"attributes"===e[o].type)){let t=e[o].target,n=e[o].attributeName,a=e[o].oldValue,s=()=>{r.has(t)||r.set(t,[]),r.get(t).push({name:n,value:t.getAttribute(n)})},l=()=>{i.has(t)||i.set(t,[]),i.get(t).push(n)};t.hasAttribute(n)&&null===a?s():t.hasAttribute(n)?(l(),s()):l()}i.forEach(((e,t)=>{g(t,e)})),r.forEach(((e,t)=>{_.forEach((n=>n(t,e)))}));for(let e of n)if(!t.includes(e)&&(p.forEach((t=>t(e))),e._x_cleanups))for(;e._x_cleanups.length;)e._x_cleanups.pop()();t.forEach((e=>{e._x_ignoreSelf=!0,e._x_ignore=!0}));for(let e of t)n.includes(e)||e.isConnected&&(delete e._x_ignoreSelf,delete e._x_ignore,h.forEach((t=>t(e))),e._x_ignore=!0,e._x_ignoreSelf=!0);t.forEach((e=>{delete e._x_ignoreSelf,delete e._x_ignore})),t=null,n=null,r=null,i=null}function C(e){return N(j(e))}function $(e,t,n){return e._x_dataStack=[t,...j(n||e)],()=>{e._x_dataStack=e._x_dataStack.filter((e=>e!==t))}}function j(e){return e._x_dataStack?e._x_dataStack:"function"==typeof ShadowRoot&&e instanceof ShadowRoot?j(e.host):e.parentNode?j(e.parentNode):[]}function N(e){let t=new Proxy({},{ownKeys:()=>Array.from(new Set(e.flatMap((e=>Object.keys(e))))),has:(t,n)=>e.some((e=>e.hasOwnProperty(n))),get:(n,r)=>(e.find((e=>{if(e.hasOwnProperty(r)){let n=Object.getOwnPropertyDescriptor(e,r);if(n.get&&n.get._x_alreadyBound||n.set&&n.set._x_alreadyBound)return!0;if((n.get||n.set)&&n.enumerable){let i=n.get,o=n.set,a=n;i=i&&i.bind(t),o=o&&o.bind(t),i&&(i._x_alreadyBound=!0),o&&(o._x_alreadyBound=!0),Object.defineProperty(e,r,{...a,get:i,set:o})}return!0}return!1}))||{})[r],set:(t,n,r)=>{let i=e.find((e=>e.hasOwnProperty(n)));return i?i[n]=r:e[e.length-1][n]=r,!0}});return t}function M(e){let t=(n,r="")=>{Object.entries(Object.getOwnPropertyDescriptors(n)).forEach((([i,{value:o,enumerable:a}])=>{if(!1===a||void 0===o)return;let s=""===r?i:`${r}.${i}`;var l;"object"==typeof o&&null!==o&&o._x_interceptor?n[i]=o.initialize(e,s,i):"object"!=typeof(l=o)||Array.isArray(l)||null===l||o===n||o instanceof Element||t(o,s)}))};return t(e)}function L(e,t=(()=>{})){let n={initialValue:void 0,_x_interceptor:!0,initialize(t,n,r){return e(this.initialValue,(()=>function(e,t){return t.split(".").reduce(((e,t)=>e[t]),e)}(t,n)),(e=>P(t,n,e)),n,r)}};return t(n),e=>{if("object"==typeof e&&null!==e&&e._x_interceptor){let t=n.initialize.bind(n);n.initialize=(r,i,o)=>{let a=e.initialize(r,i,o);return n.initialValue=a,t(r,i,o)}}else n.initialValue=e;return n}}function P(e,t,n){if("string"==typeof t&&(t=t.split(".")),1!==t.length){if(0===t.length)throw error;return e[t[0]]||(e[t[0]]={}),P(e[t[0]],t.slice(1),n)}e[t[0]]=n}var T={};function R(e,t){T[e]=t}function B(e,t){return Object.entries(T).forEach((([n,r])=>{let i=null;Object.defineProperty(e,`$${n}`,{get:()=>r(t,function(){if(i)return i;{let[e,n]=re(t);return i={interceptor:L,...e},x(t,n),i}}()),enumerable:!1})})),e}function z(e,t,n,...r){try{return n(...r)}catch(n){I(n,e,t)}}function I(e,t,n){Object.assign(e,{el:t,expression:n}),console.warn(`Alpine Expression Error: ${e.message}\n\n${n?'Expression: "'+n+'"\n\n':""}`,t),setTimeout((()=>{throw e}),0)}var D=!0;function q(e){let t=D;D=!1;let n=e();return D=t,n}function F(e,t,n={}){let r;return W(e,t)((e=>r=e),n),r}function W(...e){return U(...e)}var U=V;function V(e,t){let n={};B(n,e);let r=[n,...j(e)],i="function"==typeof t?function(e,t){return(n=(()=>{}),{scope:r={},params:i=[]}={})=>{H(n,t.apply(N([r,...e]),i))}}(r,t):function(e,t,n){let r=function(e,t){if(K[e])return K[e];let n=Object.getPrototypeOf((async function(){})).constructor,r=/^[\n\s]*if.*\(.*\)/.test(e)||/^(let|const)\s/.test(e)?`(async()=>{ ${e} })()`:e;let i=(()=>{try{return new n(["__self","scope"],`with (scope) { __self.result = ${r} }; __self.finished = true; return __self.result;`)}catch(n){return I(n,t,e),Promise.resolve()}})();return K[e]=i,i}(t,n);return(i=(()=>{}),{scope:o={},params:a=[]}={})=>{r.result=void 0,r.finished=!1;let s=N([o,...e]);if("function"==typeof r){let e=r(r,s).catch((e=>I(e,n,t)));r.finished?(H(i,r.result,s,a,n),r.result=void 0):e.then((e=>{H(i,e,s,a,n)})).catch((e=>I(e,n,t))).finally((()=>r.result=void 0))}}}(r,t,e);return z.bind(null,e,t,i)}var K={};function H(e,t,n,r,i){if(D&&"function"==typeof t){let o=t.apply(n,r);o instanceof Promise?o.then((t=>H(e,t,n,r))).catch((e=>I(e,i,t))):e(o)}else"object"==typeof t&&t instanceof Promise?t.then((t=>e(t))):e(t)}var J="x-";function Z(e=""){return J+e}var X={};function Y(e,t){return X[e]=t,{before(t){if(!X[t])return void console.warn("Cannot find directive `${directive}`. `${name}` will use the default order of execution");const n=fe.indexOf(t);fe.splice(n>=0?n:fe.indexOf("DEFAULT"),0,e)}}}function G(e,t,n){if(t=Array.from(t),e._x_virtualDirectives){let n=Object.entries(e._x_virtualDirectives).map((([e,t])=>({name:e,value:t}))),r=Q(n);n=n.map((e=>r.find((t=>t.name===e.name))?{name:`x-bind:${e.name}`,value:`"${e.value}"`}:e)),t=t.concat(n)}let r={},i=t.map(oe(((e,t)=>r[e]=t))).filter(le).map(function(e,t){return({name:n,value:r})=>{let i=n.match(ce()),o=n.match(/:([a-zA-Z0-9\-:]+)/),a=n.match(/\.[^.\]]+(?=[^\]]*$)/g)||[],s=t||e[n]||n;return{type:i?i[1]:null,value:o?o[1]:null,modifiers:a.map((e=>e.replace(".",""))),expression:r,original:s}}}(r,n)).sort(de);return i.map((t=>function(e,t){let n=()=>{},r=X[t.type]||n,[i,o]=re(e);!function(e,t,n){e._x_attributeCleanups||(e._x_attributeCleanups={}),e._x_attributeCleanups[t]||(e._x_attributeCleanups[t]=[]),e._x_attributeCleanups[t].push(n)}(e,t.original,o);let a=()=>{e._x_ignore||e._x_ignoreSelf||(r.inline&&r.inline(e,t,i),r=r.bind(r,e,t,i),ee?te.get(ne).push(r):r())};return a.runCleanups=o,a}(e,t)))}function Q(e){return Array.from(e).map(oe()).filter((e=>!le(e)))}var ee=!1,te=new Map,ne=Symbol();function re(e){let r=[],[i,o]=function(e){let r=()=>{};return[i=>{let o=t(i);return e._x_effects||(e._x_effects=new Set,e._x_runEffects=()=>{e._x_effects.forEach((e=>e()))}),e._x_effects.add(o),r=()=>{void 0!==o&&(e._x_effects.delete(o),n(o))},o},()=>{r()}]}(e);r.push(o);return[{Alpine:et,effect:i,cleanup:e=>r.push(e),evaluateLater:W.bind(W,e),evaluate:F.bind(F,e)},()=>r.forEach((e=>e()))]}var ie=(e,t)=>({name:n,value:r})=>(n.startsWith(e)&&(n=n.replace(e,t)),{name:n,value:r});function oe(e=(()=>{})){return({name:t,value:n})=>{let{name:r,value:i}=ae.reduce(((e,t)=>t(e)),{name:t,value:n});return r!==t&&e(r,t),{name:r,value:i}}}var ae=[];function se(e){ae.push(e)}function le({name:e}){return ce().test(e)}var ce=()=>new RegExp(`^${J}([^:^.]+)\\b`);var ue="DEFAULT",fe=["ignore","ref","data","id","bind","init","for","model","modelable","transition","show","if",ue,"teleport"];function de(e,t){let n=-1===fe.indexOf(e.type)?ue:e.type,r=-1===fe.indexOf(t.type)?ue:t.type;return fe.indexOf(n)-fe.indexOf(r)}function _e(e,t,n={}){e.dispatchEvent(new CustomEvent(t,{detail:n,bubbles:!0,composed:!0,cancelable:!0}))}function pe(e,t){if("function"==typeof ShadowRoot&&e instanceof ShadowRoot)return void Array.from(e.children).forEach((e=>pe(e,t)));let n=!1;if(t(e,(()=>n=!0)),n)return;let r=e.firstElementChild;for(;r;)pe(r,t),r=r.nextElementSibling}function he(e,...t){console.warn(`Alpine Warning: ${e}`,...t)}var xe=!1;var ge=[],me=[];function ve(){return ge.map((e=>e()))}function ye(){return ge.concat(me).map((e=>e()))}function be(e){ge.push(e)}function we(e){me.push(e)}function Ee(e,t=!1){return Ae(e,(e=>{if((t?ye():ve()).some((t=>e.matches(t))))return!0}))}function Ae(e,t){if(e){if(t(e))return e;if(e._x_teleportBack&&(e=e._x_teleportBack),e.parentElement)return Ae(e.parentElement,t)}}var Oe=[];function ke(e,t=pe,n=(()=>{})){!function(e){ee=!0;let t=Symbol();ne=t,te.set(t,[]);let n=()=>{for(;te.get(t).length;)te.get(t).shift()();te.delete(t)};e(n),ee=!1,n()}((()=>{t(e,((e,t)=>{n(e,t),Oe.forEach((n=>n(e,t))),G(e,e.attributes).forEach((e=>e())),e._x_ignore&&t()}))}))}function Se(e){pe(e,(e=>g(e)))}var Ce=[],$e=!1;function je(e=(()=>{})){return queueMicrotask((()=>{$e||setTimeout((()=>{Ne()}))})),new Promise((t=>{Ce.push((()=>{e(),t()}))}))}function Ne(){for($e=!1;Ce.length;)Ce.shift()()}function Me(e,t){return Array.isArray(t)?Le(e,t.join(" ")):"object"==typeof t&&null!==t?function(e,t){let n=e=>e.split(" ").filter(Boolean),r=Object.entries(t).flatMap((([e,t])=>!!t&&n(e))).filter(Boolean),i=Object.entries(t).flatMap((([e,t])=>!t&&n(e))).filter(Boolean),o=[],a=[];return i.forEach((t=>{e.classList.contains(t)&&(e.classList.remove(t),a.push(t))})),r.forEach((t=>{e.classList.contains(t)||(e.classList.add(t),o.push(t))})),()=>{a.forEach((t=>e.classList.add(t))),o.forEach((t=>e.classList.remove(t)))}}(e,t):"function"==typeof t?Me(e,t()):Le(e,t)}function Le(e,t){return t=!0===t?t="":t||"",n=t.split(" ").filter((t=>!e.classList.contains(t))).filter(Boolean),e.classList.add(...n),()=>{e.classList.remove(...n)};var n}function Pe(e,t){return"object"==typeof t&&null!==t?function(e,t){let n={};return Object.entries(t).forEach((([t,r])=>{n[t]=e.style[t],t.startsWith("--")||(t=t.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()),e.style.setProperty(t,r)})),setTimeout((()=>{0===e.style.length&&e.removeAttribute("style")})),()=>{Pe(e,n)}}(e,t):function(e,t){let n=e.getAttribute("style",t);return e.setAttribute("style",t),()=>{e.setAttribute("style",n||"")}}(e,t)}function Te(e,t=(()=>{})){let n=!1;return function(){n?t.apply(this,arguments):(n=!0,e.apply(this,arguments))}}function Re(e,t,n={}){e._x_transition||(e._x_transition={enter:{during:n,start:n,end:n},leave:{during:n,start:n,end:n},in(n=(()=>{}),r=(()=>{})){ze(e,t,{during:this.enter.during,start:this.enter.start,end:this.enter.end},n,r)},out(n=(()=>{}),r=(()=>{})){ze(e,t,{during:this.leave.during,start:this.leave.start,end:this.leave.end},n,r)}})}function Be(e){let t=e.parentNode;if(t)return t._x_hidePromise?t:Be(t)}function ze(e,t,{during:n,start:r,end:i}={},o=(()=>{}),a=(()=>{})){if(e._x_transitioning&&e._x_transitioning.cancel(),0===Object.keys(n).length&&0===Object.keys(r).length&&0===Object.keys(i).length)return o(),void a();let s,l,c;!function(e,t){let n,r,i,o=Te((()=>{A((()=>{n=!0,r||t.before(),i||(t.end(),Ne()),t.after(),e.isConnected&&t.cleanup(),delete e._x_transitioning}))}));e._x_transitioning={beforeCancels:[],beforeCancel(e){this.beforeCancels.push(e)},cancel:Te((function(){for(;this.beforeCancels.length;)this.beforeCancels.shift()();o()})),finish:o},A((()=>{t.start(),t.during()})),$e=!0,requestAnimationFrame((()=>{if(n)return;let o=1e3*Number(getComputedStyle(e).transitionDuration.replace(/,.*/,"").replace("s","")),a=1e3*Number(getComputedStyle(e).transitionDelay.replace(/,.*/,"").replace("s",""));0===o&&(o=1e3*Number(getComputedStyle(e).animationDuration.replace("s",""))),A((()=>{t.before()})),r=!0,requestAnimationFrame((()=>{n||(A((()=>{t.end()})),Ne(),setTimeout(e._x_transitioning.finish,o+a),i=!0)}))}))}(e,{start(){s=t(e,r)},during(){l=t(e,n)},before:o,end(){s(),c=t(e,i)},after:a,cleanup(){l(),c()}})}function Ie(e,t,n){if(-1===e.indexOf(t))return n;const r=e[e.indexOf(t)+1];if(!r)return n;if("scale"===t&&isNaN(r))return n;if("duration"===t||"delay"===t){let e=r.match(/([0-9]+)ms/);if(e)return e[1]}return"origin"===t&&["top","right","left","center","bottom"].includes(e[e.indexOf(t)+2])?[r,e[e.indexOf(t)+2]].join(" "):r}Y("transition",((e,{value:t,modifiers:n,expression:r},{evaluate:i})=>{"function"==typeof r&&(r=i(r)),!1!==r&&(r&&"boolean"!=typeof r?function(e,t,n){Re(e,Me,""),{enter:t=>{e._x_transition.enter.during=t},"enter-start":t=>{e._x_transition.enter.start=t},"enter-end":t=>{e._x_transition.enter.end=t},leave:t=>{e._x_transition.leave.during=t},"leave-start":t=>{e._x_transition.leave.start=t},"leave-end":t=>{e._x_transition.leave.end=t}}[n](t)}(e,r,t):function(e,t,n){Re(e,Pe);let r=!t.includes("in")&&!t.includes("out")&&!n,i=r||t.includes("in")||["enter"].includes(n),o=r||t.includes("out")||["leave"].includes(n);t.includes("in")&&!r&&(t=t.filter(((e,n)=>nn>t.indexOf("out"))));let a=!t.includes("opacity")&&!t.includes("scale"),s=a||t.includes("opacity"),l=a||t.includes("scale"),c=s?0:1,u=l?Ie(t,"scale",95)/100:1,f=Ie(t,"delay",0)/1e3,d=Ie(t,"origin","center"),_="opacity, transform",p=Ie(t,"duration",150)/1e3,h=Ie(t,"duration",75)/1e3,x="cubic-bezier(0.4, 0.0, 0.2, 1)";i&&(e._x_transition.enter.during={transformOrigin:d,transitionDelay:`${f}s`,transitionProperty:_,transitionDuration:`${p}s`,transitionTimingFunction:x},e._x_transition.enter.start={opacity:c,transform:`scale(${u})`},e._x_transition.enter.end={opacity:1,transform:"scale(1)"});o&&(e._x_transition.leave.during={transformOrigin:d,transitionDelay:`${f}s`,transitionProperty:_,transitionDuration:`${h}s`,transitionTimingFunction:x},e._x_transition.leave.start={opacity:1,transform:"scale(1)"},e._x_transition.leave.end={opacity:c,transform:`scale(${u})`})}(e,n,t))})),window.Element.prototype._x_toggleAndCascadeWithTransitions=function(e,t,n,r){const i="visible"===document.visibilityState?requestAnimationFrame:setTimeout;let o=()=>i(n);t?e._x_transition&&(e._x_transition.enter||e._x_transition.leave)?e._x_transition.enter&&(Object.entries(e._x_transition.enter.during).length||Object.entries(e._x_transition.enter.start).length||Object.entries(e._x_transition.enter.end).length)?e._x_transition.in(n):o():e._x_transition?e._x_transition.in(n):o():(e._x_hidePromise=e._x_transition?new Promise(((t,n)=>{e._x_transition.out((()=>{}),(()=>t(r))),e._x_transitioning.beforeCancel((()=>n({isFromCancelledTransition:!0})))})):Promise.resolve(r),queueMicrotask((()=>{let t=Be(e);t?(t._x_hideChildren||(t._x_hideChildren=[]),t._x_hideChildren.push(e)):i((()=>{let t=e=>{let n=Promise.all([e._x_hidePromise,...(e._x_hideChildren||[]).map(t)]).then((([e])=>e()));return delete e._x_hidePromise,delete e._x_hideChildren,n};t(e).catch((e=>{if(!e.isFromCancelledTransition)throw e}))}))})))};var De=!1;function qe(e,t=(()=>{})){return(...n)=>De?t(...n):e(...n)}function Fe(t,n,r,i=[]){switch(t._x_bindings||(t._x_bindings=e({})),t._x_bindings[n]=r,n=i.includes("camel")?n.toLowerCase().replace(/-(\w)/g,((e,t)=>t.toUpperCase())):n){case"value":!function(e,t){if("radio"===e.type)void 0===e.attributes.value&&(e.value=t),window.fromModel&&(e.checked=Ue(e.value,t));else if("checkbox"===e.type)Number.isInteger(t)?e.value=t:Number.isInteger(t)||Array.isArray(t)||"boolean"==typeof t||[null,void 0].includes(t)?Array.isArray(t)?e.checked=t.some((t=>Ue(t,e.value))):e.checked=!!t:e.value=String(t);else if("SELECT"===e.tagName)!function(e,t){const n=[].concat(t).map((e=>e+""));Array.from(e.options).forEach((e=>{e.selected=n.includes(e.value)}))}(e,t);else{if(e.value===t)return;e.value=t}}(t,r);break;case"style":!function(e,t){e._x_undoAddedStyles&&e._x_undoAddedStyles();e._x_undoAddedStyles=Pe(e,t)}(t,r);break;case"class":!function(e,t){e._x_undoAddedClasses&&e._x_undoAddedClasses();e._x_undoAddedClasses=Me(e,t)}(t,r);break;case"selected":case"checked":!function(e,t,n){We(e,t,n),function(e,t,n){e[t]!==n&&(e[t]=n)}(e,t,n)}(t,n,r);break;default:We(t,n,r)}}function We(e,t,n){[null,void 0,!1].includes(n)&&function(e){return!["aria-pressed","aria-checked","aria-expanded","aria-selected"].includes(e)}(t)?e.removeAttribute(t):(Ve(t)&&(n=t),function(e,t,n){e.getAttribute(t)!=n&&e.setAttribute(t,n)}(e,t,n))}function Ue(e,t){return e==t}function Ve(e){return["disabled","checked","required","readonly","hidden","open","selected","autofocus","itemscope","multiple","novalidate","allowfullscreen","allowpaymentrequest","formnovalidate","autoplay","controls","loop","muted","playsinline","default","ismap","reversed","async","defer","nomodule"].includes(e)}function Ke(e,t,n){let r=e.getAttribute(t);return null===r?"function"==typeof n?n():n:""===r||(Ve(t)?!![t,"true"].includes(r):r)}function He(e,t){var n;return function(){var r=this,i=arguments,o=function(){n=null,e.apply(r,i)};clearTimeout(n),n=setTimeout(o,t)}}function Je(e,t){let n;return function(){let r=this,i=arguments;n||(e.apply(r,i),n=!0,setTimeout((()=>n=!1),t))}}var Ze={},Xe=!1;var Ye={};function Ge(e,t,n){let r=[];for(;r.length;)r.pop()();let i=Object.entries(t).map((([e,t])=>({name:e,value:t}))),o=Q(i);i=i.map((e=>o.find((t=>t.name===e.name))?{name:`x-bind:${e.name}`,value:`"${e.value}"`}:e)),G(e,i,n).map((e=>{r.push(e.runCleanups),e()}))}var Qe={};var et={get reactive(){return e},get release(){return n},get effect(){return t},get raw(){return r},version:"3.12.3",flushAndStopDeferringMutations:function(){O=!1,S(k),k=[]},dontAutoEvaluateFunctions:q,disableEffectScheduling:function(e){f=!1,e(),f=!0},startObservingMutations:y,stopObservingMutations:b,setReactivityEngine:function(i){e=i.reactive,n=i.release,t=e=>i.effect(e,{scheduler:e=>{f?l(e):e()}}),r=i.raw},closestDataStack:j,skipDuringClone:qe,onlyDuringClone:function(e){return(...t)=>De&&e(...t)},addRootSelector:be,addInitSelector:we,addScopeToNode:$,deferMutations:function(){O=!0},mapAttributes:se,evaluateLater:W,interceptInit:function(e){Oe.push(e)},setEvaluator:function(e){U=e},mergeProxies:N,extractProp:function(e,t,n,r=!0){if(e._x_bindings&&void 0!==e._x_bindings[t])return e._x_bindings[t];if(e._x_inlineBindings&&void 0!==e._x_inlineBindings[t]){let n=e._x_inlineBindings[t];return n.extract=r,q((()=>F(e,n.expression)))}return Ke(e,t,n)},findClosest:Ae,closestRoot:Ee,destroyTree:Se,interceptor:L,transition:ze,setStyles:Pe,mutateDom:A,directive:Y,throttle:Je,debounce:He,evaluate:F,initTree:ke,nextTick:je,prefixed:Z,prefix:function(e){J=e},plugin:function(e){(Array.isArray(e)?e:[e]).forEach((e=>e(et)))},magic:R,store:function(t,n){if(Xe||(Ze=e(Ze),Xe=!0),void 0===n)return Ze[t];Ze[t]=n,"object"==typeof n&&null!==n&&n.hasOwnProperty("init")&&"function"==typeof n.init&&Ze[t].init(),M(Ze[t])},start:function(){var e;xe&&he("Alpine has already been initialized on this page. Calling Alpine.start() more than once can cause problems."),xe=!0,document.body||he("Unable to initialize. Trying to load Alpine before `` is available. Did you forget to add `defer` in Alpine's `
+
@@ -1022,6 +1023,5 @@
@livewireScripts
-