From 0e69e4248c2005b5197515518c4d3b7b85e1d70d Mon Sep 17 00:00:00 2001 From: Cram42 Date: Thu, 27 Oct 2022 08:12:42 +0800 Subject: [PATCH 01/39] Fix: Multiple outputs at hardware/{id}/label --- app/Http/Controllers/Assets/AssetsController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php index c845b2cc7..b0fde82c5 100755 --- a/app/Http/Controllers/Assets/AssetsController.php +++ b/app/Http/Controllers/Assets/AssetsController.php @@ -543,7 +543,7 @@ class AssetsController extends Controller $this->authorize('view', $asset); return view('hardware/labels') - ->with('assets', Asset::find($asset)) + ->with('assets', collect([ $asset ])) ->with('settings', Setting::getSettings()) ->with('bulkedit', false) ->with('count', 0); From 35536b5dbde6b53afa181de9fec23dd02d2fde31 Mon Sep 17 00:00:00 2001 From: Cram42 Date: Tue, 1 Nov 2022 19:47:59 +0800 Subject: [PATCH 02/39] Require tecnickcom/tcpdf --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 07dbf2063..ccddd5ab2 100644 --- a/composer.json +++ b/composer.json @@ -68,6 +68,7 @@ "rollbar/rollbar-laravel": "^7.0", "spatie/laravel-backup": "^6.16", "tecnickcom/tc-lib-barcode": "^1.15", + "tecnickcom/tcpdf": "^6.5.0", "unicodeveloper/laravel-password": "^1.0", "watson/validating": "^6.1" }, From 7c355cef2d3a8f3d67795d0ea93b9032fa09b17e Mon Sep 17 00:00:00 2001 From: Cram42 Date: Tue, 1 Nov 2022 19:49:57 +0800 Subject: [PATCH 03/39] Add helper to convert between units of measurement --- app/Helpers/Helper.php | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/app/Helpers/Helper.php b/app/Helpers/Helper.php index cfb0aa9f1..e3e8fd75c 100644 --- a/app/Helpers/Helper.php +++ b/app/Helpers/Helper.php @@ -1126,4 +1126,43 @@ class Helper return $settings; } + /** + * 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'); + } + } + } From df89406987623a125ace41dec58d561fc18b6622 Mon Sep 17 00:00:00 2001 From: Cram42 Date: Tue, 1 Nov 2022 19:52:34 +0800 Subject: [PATCH 04/39] Create Label model and example --- app/Models/Labels/Label.php | 591 ++++++++++++++++++ app/Models/Labels/RectangleSheet.php | 48 ++ app/Models/Labels/Sheet.php | 209 +++++++ app/Models/Labels/Sheets/Avery/L7162.php | 71 +++ .../Labels/Sheets/Avery/L7162_Example.php | 91 +++ 5 files changed, 1010 insertions(+) create mode 100644 app/Models/Labels/Label.php create mode 100644 app/Models/Labels/RectangleSheet.php create mode 100644 app/Models/Labels/Sheet.php create mode 100644 app/Models/Labels/Sheets/Avery/L7162.php create mode 100644 app/Models/Labels/Sheets/Avery/L7162_Example.php diff --git a/app/Models/Labels/Label.php b/app/Models/Labels/Label.php new file mode 100644 index 000000000..248044163 --- /dev/null +++ b/app/Models/Labels/Label.php @@ -0,0 +1,591 @@ +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($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_Example.php b/app/Models/Labels/Sheets/Avery/L7162_Example.php new file mode 100644 index 000000000..10d1a4f9e --- /dev/null +++ b/app/Models/Labels/Sheets/Avery/L7162_Example.php @@ -0,0 +1,91 @@ +getLabelPrintableArea(); + + $x = $pa->x1; + $y = $pa->y1; + $w = $pa->w; + + static::write1DBarcode( + $pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type, + $pa->x1, $pa->y2 - self::BARCODE1D_SIZE, + $pa->w, self::BARCODE1D_SIZE + ); + + if ($record->get('logo')) { + $logoMaxHeight = $pa->h - self::BARCODE1D_SIZE - self::LOGO_MARGIN; + $logoSize = static::writeImage( + $pdf, $record->get('logo'), + $x, $y, + self::LOGO_MAX_WIDTH, $logoMaxHeight, + 'L', 'T', 300, + true, false, 0 + ); + $x += $logoSize[0] + self::LOGO_MARGIN; + $w -= ($logoSize[0] + self::LOGO_MARGIN); + } + + if ($record->get('title')) { + static::writeText( + $pdf, $record->get('title'), + $x, $y, + 'freesans', '', self::TITLE_SIZE, 'L', + $w, self::TITLE_SIZE, true, 0 + ); + $y += self::TITLE_SIZE + self::TITLE_MARGIN; + } + + foreach ($record->get('fields') as $label => $value) { + static::writeText( + $pdf, $label, + $x, $y, + 'freesans', '', self::LABEL_SIZE, 'L', + $w, self::LABEL_SIZE, true, 0 + ); + $y += self::LABEL_SIZE + self::LABEL_MARGIN; + + static::writeText( + $pdf, $value, + $x, $y, + 'freemono', 'B', self::FIELD_SIZE, 'L', + $w, self::FIELD_SIZE, true, 0, 0.3 + ); + $y += self::FIELD_SIZE + self::FIELD_MARGIN; + } + + } +} + + +?> \ No newline at end of file From bbecdb6768501f6da6699910cefcb3d8f21fca4f Mon Sep 17 00:00:00 2001 From: Cram42 Date: Tue, 1 Nov 2022 19:53:11 +0800 Subject: [PATCH 05/39] Add QR example --- .../Labels/Sheets/Avery/L7162_Example_QR.php | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 app/Models/Labels/Sheets/Avery/L7162_Example_QR.php diff --git a/app/Models/Labels/Sheets/Avery/L7162_Example_QR.php b/app/Models/Labels/Sheets/Avery/L7162_Example_QR.php new file mode 100644 index 000000000..54c886e6e --- /dev/null +++ b/app/Models/Labels/Sheets/Avery/L7162_Example_QR.php @@ -0,0 +1,77 @@ +getLabelPrintableArea(); + + static::write2DBarcode( + $pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type, + $pa->x1, $pa->y1, + $pa->h, $pa->h + ); + + $x = $pa->x1 + $pa->h + self::BARCODE2D_MARGIN; + $y = $pa->y1; + $w = $pa->w - $pa->h - self::BARCODE2D_MARGIN; + + if ($record->get('title')) { + static::writeText( + $pdf, $record->get('title'), + $x, $y, + 'freesans', '', self::TITLE_SIZE, 'L', + $w, self::TITLE_SIZE, true, 0 + ); + $y += self::TITLE_SIZE + self::TITLE_MARGIN; + } + + foreach ($record->get('fields') as $label => $value) { + static::writeText( + $pdf, $label, + $x, $y, + 'freesans', '', self::LABEL_SIZE, 'L', + $w, self::LABEL_SIZE, true, 0 + ); + $y += self::LABEL_SIZE + self::LABEL_MARGIN; + + static::writeText( + $pdf, $value, + $x, $y, + 'freemono', 'B', self::FIELD_SIZE, 'L', + $w, self::FIELD_SIZE, true, 0, 0.3 + ); + $y += self::FIELD_SIZE + self::FIELD_MARGIN; + } + + } +} + + +?> \ No newline at end of file From 4ed728d954b9a6a80f366733e5d5602dfb855ae4 Mon Sep 17 00:00:00 2001 From: Cram42 Date: Tue, 1 Nov 2022 19:54:00 +0800 Subject: [PATCH 06/39] Add template to simulate legacy label --- app/Models/Labels/DefaultLabel.php | 224 +++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 app/Models/Labels/DefaultLabel.php diff --git a/app/Models/Labels/DefaultLabel.php b/app/Models/Labels/DefaultLabel.php new file mode 100644 index 000000000..103e3181e --- /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 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 = Asset::find($record->get('id')); + $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 From bb09f0168f3f6d9f81f2e116a665d3f64f4bb28f Mon Sep 17 00:00:00 2001 From: Cram42 Date: Tue, 1 Nov 2022 19:56:53 +0800 Subject: [PATCH 07/39] Create Label View/Generator --- app/View/Label.php | 243 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 app/View/Label.php diff --git a/app/View/Label.php b/app/View/Label.php new file mode 100644 index 000000000..c720f5a8d --- /dev/null +++ b/app/View/Label.php @@ -0,0 +1,243 @@ +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'); + + // If disabled, pass to legacy view + if (!$settings->label2_enable) { + return view('hardware/labels') + ->with('assets', $assets) + ->with('settings', $settings) + ->with('bulkedit', $this->data->get('bulkedit')) + ->with('count', $this->data->get('count')); + } + + + $template = LabelModel::find($settings->label2_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); + + // 'Label1=field1|Alt1=altfield1;Label2=field2;Alt1=altfield1|Label3=field3' + $fieldDefinitions = (new Collection()) + ->merge(explode(';', $settings->label2_fields)) + ->filter(function ($defString) { + return strpos($defString, '=') !== false; + }) + ->map(function ($defString) { + return (new Collection()) + ->merge(explode('|', $defString)) // ['Label1=field1', 'Alt1=altfield1'] + ->mapWithKeys(function ($altString) { + $parts = explode('=', $altString); + if (count($parts) != 2) throw new \Exception(var_export($parts, true)); + return [ $parts[0] => $parts[1] ]; + }); + }); + /* + $fieldDefinitions should now look like: + [ + [ + 'Label1'=>'field1', + 'Alt1'=>'altfield1' + ], + [ + 'Label2'=>'field2' + ], + [ + 'Alt1'=>'altfield1', + 'Label3'=>'field3' + ] + ] + */ + + // Prepare data + $data = $assets + ->map(function ($asset) use ($template, $settings, $fieldDefinitions) { + + $assetData = new Collection(); + + $assetData->put('id', $asset->id); + $assetData->put('tag', $asset->asset_tag); + + if ($template->getSupportTitle()) { + $assetData->put('title', !empty($settings->label2_title) ? + str_ireplace(':company', $asset->company->name, $settings->label2_title) : + $settings->qr_text + ); + } + + if ($template->getSupportLogo()) { + $assetData->put('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 ($template->getSupport1DBarcode()) { + $barcode1DType = $settings->label2_1d_type; + $barcode1DType = ($barcode1DType == 'default') ? + (($settings->alt_barcode_enabled) ? $settings->alt_barcode : null) : + $barcode1DType; + if ($barcode1DType) { + $assetData->put('barcode1d', (object)[ + 'type' => $barcode1DType, + 'content' => $asset->asset_tag, + ]); + } + } + + if ($template->getSupport2DBarcode()) { + $barcode2DType = $settings->label2_2d_type; + $barcode2DType = ($barcode2DType == 'default') ? + (($settings->qr_code) ? $settings->barcode_type : null) : + $barcode2DType; + if ($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(function ($group, $index) use ($asset) { + return $group->mapWithKeys(function ($definition, $label) use ($asset) { + $value = collect(explode('.', $definition)) + ->reduce(function ($carry, $chunk) { + return $carry ? $carry->{$chunk} : ${$carry}; + }, $asset); + return [ $label => $value ]; + }); + }) + ->reduce(function ($carry, $group, $index) { + $values = $group + ->filter(function ($value, $label) use ($carry) { + if (empty($value)) return false; + if ($carry->has($label)) return false; + return true; + }) + ->take(1); + return $carry->merge($values); + }, 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 From 5558a005b9c4dff48e32585a85401c10b99324d4 Mon Sep 17 00:00:00 2001 From: Cram42 Date: Tue, 1 Nov 2022 19:57:51 +0800 Subject: [PATCH 08/39] Implement API for labels --- app/Http/Controllers/Api/LabelsController.php | 71 +++++++++++++++++++ app/Http/Transformers/LabelsTransformer.php | 70 ++++++++++++++++++ routes/api.php | 10 +++ 3 files changed, 151 insertions(+) create mode 100644 app/Http/Controllers/Api/LabelsController.php create mode 100644 app/Http/Transformers/LabelsTransformer.php 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/Transformers/LabelsTransformer.php b/app/Http/Transformers/LabelsTransformer.php new file mode 100644 index 000000000..f575f91d6 --- /dev/null +++ b/app/Http/Transformers/LabelsTransformer.php @@ -0,0 +1,70 @@ +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_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/routes/api.php b/routes/api.php index d27f14601..19ec4fd74 100644 --- a/routes/api.php +++ b/routes/api.php @@ -611,6 +611,16 @@ Route::group(['prefix' => 'v1', 'middleware' => ['api', 'throttle:api']], functi ); // end imports API routes + /** + * Labels API routes + */ + Route::group(['prefix' => 'labels'], function() { + Route::get('{name}', [ Api\LabelsController::class, 'show']) + ->where('name', '.*') + ->name('api.labels.show'); + Route::get('', [ Api\LabelsController::class, 'index']) + ->name('api.labels.index'); + }); /** * Licenses API routes From 6de48b4dc8cd92cbade99a3b819d1be5c3abbaeb Mon Sep 17 00:00:00 2001 From: Cram42 Date: Tue, 1 Nov 2022 20:00:53 +0800 Subject: [PATCH 09/39] Implement settings for labels --- app/Http/Controllers/SettingsController.php | 8 + app/Presenters/LabelPresenter.php | 88 ++++ .../2022_10_25_215520_add_label2_settings.php | 51 ++ .../views/partials/bootstrap-table.blade.php | 19 + resources/views/settings/labels.blade.php | 457 +++++++++++++----- 5 files changed, 496 insertions(+), 127 deletions(-) create mode 100644 app/Presenters/LabelPresenter.php create mode 100644 database/migrations/2022_10_25_215520_add_label2_settings.php diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php index 111eb1bae..6c286330b 100755 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -844,6 +844,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/Presenters/LabelPresenter.php b/app/Presenters/LabelPresenter.php new file mode 100644 index 000000000..387118432 --- /dev/null +++ b/app/Presenters/LabelPresenter.php @@ -0,0 +1,88 @@ + '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_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/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..66b0a0871 --- /dev/null +++ b/database/migrations/2022_10_25_215520_add_label2_settings.php @@ -0,0 +1,51 @@ +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.tag').'=asset_tag;'. + 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/resources/views/partials/bootstrap-table.blade.php b/resources/views/partials/bootstrap-table.blade.php index 57ac6a4d7..de888dbe7 100644 --- a/resources/views/partials/bootstrap-table.blade.php +++ b/resources/views/partials/bootstrap-table.blade.php @@ -663,6 +663,25 @@ } } + function labelPerPageFormatter(value, row, index, field) { + if (row) { + if (row.sheet_labels) { return 1; } + else { return row.sheet_info.labels_per_page; } + } + } + + function labelRadioFormatter(value, row, index, field) { + if (row) { + return row.name == '{{ str_replace("\\", "\\\\", $snipeSettings->label2_template) }}'; + } + } + + function labelSizeFormatter(value, row) { + if (row) { + return row.width + ' x ' + row.height + ' ' + row.unit; + } + } + function cleanFloat(number) { if(!number) { // in a JavaScript context, meaning, if it's null or zero or unset return 0.0; diff --git a/resources/views/settings/labels.blade.php b/resources/views/settings/labels.blade.php index a509769de..dd9b302b2 100644 --- a/resources/views/settings/labels.blade.php +++ b/resources/views/settings/labels.blade.php @@ -40,160 +40,363 @@
-
+ +
- {{ Form::label('labels_per_page', trans('admin/settings/general.labels_per_page')) }} + {{ Form::label('label2_enable', trans('admin/settings/general.label2_enable')) }}
- {{ Form::text('labels_per_page', old('labels_per_page', $setting->labels_per_page), ['class' => 'form-control','style' => 'width: 100px;', 'aria-label'=>'labels_per_page']) }} - {!! $errors->first('labels_per_page', '') !!} + {{ Form::checkbox('label2_enable', '1', old('label2_enable', $setting->label2_enable, [ 'class'=>'minimal', 'aria-label'=>'label2_enable' ])) }} + {{ trans('general.yes') }} + {!! $errors->first('label2_enable', '') !!} +

{!! trans('admin/settings/general.label2_enable_help') !!}

-
-
- {{ Form::label('labels_fontsize', trans('admin/settings/general.labels_fontsize')) }} -
-
-
- {{ Form::text('labels_fontsize', old('labels_fontsize', $setting->labels_fontsize), ['class' => 'form-control', 'aria-label'=>'labels_fontsize']) }} -
{{ trans('admin/settings/general.text_pt') }}
-
-
-
- {!! $errors->first('labels_fontsize', '') !!} -
-
+ @if ($setting->label2_enable) + -
-
- {{ Form::label('labels_width', trans('admin/settings/general.label_dimensions')) }} -
-
-
- {{ Form::text('labels_width', old('labels_width', $setting->labels_width), ['class' => 'form-control', 'aria-label'=>'labels_width']) }} -
{{ trans('admin/settings/general.width_w') }}
+ +
+
+ {{ Form::label('label2_template', trans('admin/settings/general.label2_template')) }} +
+
+
-
-
- {{ Form::text('labels_height', old('labels_height', $setting->labels_height), ['class' => 'form-control', 'aria-label'=>'labels_height']) }} -
{{ trans('admin/settings/general.height_h') }}
-
-
-
- {!! $errors->first('labels_width', '') !!} - {!! $errors->first('labels_height', '') !!} -
-
-
-
- {{ Form::label('labels_display_sgutter', trans('admin/settings/general.label_gutters')) }} -
-
-
- {{ Form::text('labels_display_sgutter', old('labels_display_sgutter', $setting->labels_display_sgutter), ['class' => 'form-control', 'aria-label'=>'labels_display_sgutter']) }} -
{{ trans('admin/settings/general.horizontal') }}
+ +
+
+ {{ Form::label('label2_title', trans('admin/settings/general.label2_title')) }} +
+
+ {{ Form::text('label2_title', old('label2_title', $setting->label2_title), [ 'class'=>'form-control', 'placeholder'=>$setting->qr_text, 'aria-label'=>'label2_title' ]) }} + {!! $errors->first('label2_title', '') !!} +

{!! trans('admin/settings/general.label2_title_help') !!}

+

+ {!! trans('admin/settings/general.label2_title_help_phold') !!}.
+ {!! trans('admin/settings/general.help_asterisk_bold') !!}.
+ {!! + trans('admin/settings/general.help_blank_to_use', [ + 'setting_name' => trans('admin/settings/general.barcodes').' > '.trans('admin/settings/general.qr_text') + ]) + !!} +

-
-
- {{ Form::text('labels_display_bgutter', old('labels_display_bgutter', $setting->labels_display_bgutter), ['class' => 'form-control', 'aria-label'=>'labels_display_bgutter']) }} -
{{ trans('admin/settings/general.vertical') }}
-
-
-
- {!! $errors->first('labels_display_sgutter', '') !!} - {!! $errors->first('labels_display_bgutter', '') !!} -
-
-
-
- {{ Form::label('labels_pmargin_top', trans('admin/settings/general.page_padding')) }} -
-
-
- {{ Form::text('labels_pmargin_top', old('labels_pmargin_top', $setting->labels_pmargin_top), ['class' => 'form-control', 'aria-label'=>'labels_pmargin_top']) }} -
{{ trans('admin/settings/general.top') }}
+ +
+
+ {{ Form::label('label2_asset_logo', trans('admin/settings/general.label2_asset_logo')) }}
-
- {{ Form::text('labels_pmargin_right', old('labels_pmargin_right', $setting->labels_pmargin_right), ['class' => 'form-control', 'aria-label'=>'labels_pmargin_right']) }} -
{{ trans('admin/settings/general.right') }}
+
+ {{ Form::checkbox('label2_asset_logo', '1', old('label2_asset_logo', $setting->label2_asset_logo, [ 'class'=>'minimal', 'aria-label'=>'label2_asset_logo' ])) }} + {{ trans('general.yes') }} + {!! $errors->first('label2_asset_logo', '') !!} +

{!! trans('admin/settings/general.label2_asset_logo_help', ['setting_name' => trans('admin/settings/general.brand').' > '.trans('admin/settings/general.label_logo')]) !!}

-
-
- {{ Form::text('labels_pmargin_bottom', old('labels_pmargin_bottom', $setting->labels_pmargin_bottom), ['class' => 'form-control', 'aria-label'=>'labels_pmargin_bottom']) }} -
{{ trans('admin/settings/general.bottom') }}
+ + +
+
+ {{ Form::label('label2_1d_type', trans('admin/settings/general.label2_1d_type')) }}
-
- {{ Form::text('labels_pmargin_left', old('labels_pmargin_left', $setting->labels_pmargin_left), ['class' => 'form-control', 'aria-label'=>'labels_pmargin_left']) }} -
{{ trans('admin/settings/general.left') }}
-
- -
-
- {!! $errors->first('labels_width', '') !!} - {!! $errors->first('labels_height', '') !!} -
-
- -
-
- {{ Form::label('labels_pagewidth', trans('admin/settings/general.page_dimensions')) }} -
-
-
- {{ Form::text('labels_pagewidth', old('labels_pagewidth', $setting->labels_pagewidth), ['class' => 'form-control', 'aria-label'=>'labels_pagewidth']) }} -
{{ trans('admin/settings/general.width_w') }}
+
+ @php + $select1DValues = [ + 'default' => trans('admin/settings/general.default').' [ '.$setting->alt_barcode.' ]', + 'none' => trans('admin/settings/general.none'), + 'C128' => 'C128', + 'C39' => 'C39', + 'EAN5' => 'EAN5', + 'EAN13' => 'EAN13', + 'UPCA' => 'UPCA', + 'UPCE' => 'UPCE' + ]; + @endphp + {{ Form::select('label2_1d_type', $select1DValues, old('label2_1d_type', $setting->label2_1d_type), [ 'class'=>'select2 col-md-4', 'aria-label'=>'label2_1d_type' ]) }} + {!! $errors->first('label2_1d_type', '') !!} +

+ {{ trans('admin/settings/general.label2_1d_type_help') }}. + {!! + trans('admin/settings/general.help_default_will_use', [ + 'default' => trans('admin/settings/general.default'), + 'setting_name' => trans('admin/settings/general.barcodes').' > '.trans('admin/settings/general.alt_barcode_type'), + ]) + !!} +

-
-
- {{ Form::text('labels_pageheight', old('labels_pageheight', $setting->labels_pageheight), ['class' => 'form-control', 'aria-label'=>'labels_pageheight']) }} -
{{ trans('admin/settings/general.height_h') }}
+ + +
+
+ {{ Form::label('label2_2d_type', trans('admin/settings/general.label2_2d_type')) }} +
+
+ @php + $select2DValues = [ + 'default' => trans('admin/settings/general.default').' [ '.$setting->barcode_type.' ]', + 'none' => trans('admin/settings/general.none'), + 'QRCODE' => 'QRCODE', + 'DATAMATRIX' => 'DATAMATRIX', + 'PDF417' => 'PDF417', + ]; + @endphp + {{ Form::select('label2_2d_type', $select2DValues, old('label2_2d_type', $setting->label2_2d_type), [ 'class'=>'select2 col-md-4', 'aria-label'=>'label2_2d_type' ]) }} + {!! $errors->first('label2_2d_type', '') !!} +

+ {{ trans('admin/settings/general.label2_2d_type_help', ['current' => $setting->barcode_type]) }}. + {!! + trans('admin/settings/general.help_default_will_use', [ + 'default' => trans('admin/settings/general.default'), + 'setting_name' => trans('admin/settings/general.barcodes').' > '.trans('admin/settings/general.barcode_type'), + ]) + !!} +

-
- {!! $errors->first('labels_pagewidth', '') !!} - {!! $errors->first('labels_pageheight', '') !!} + + +
+
+ {{ Form::label('label2_2d_target', trans('admin/settings/general.label2_2d_target')) }} +
+
+ {{ Form::select('label2_2d_target', ['hardware_id'=>'/hardware/{id} ('.trans('admin/settings/general.default').')', 'ht_tag'=>'/ht/{asset_tag}'], old('label2_2d_target', $setting->label2_2d_target), [ 'class'=>'select2 col-md-4', 'aria-label'=>'label2_2d_target' ]) }} + {!! $errors->first('label2_2d_target', '') !!} +

{!! trans('admin/settings/general.label2_2d_target_help') !!}

+
-
-
-
- {{ Form::label('labels_display', trans('admin/settings/general.label_fields')) }} + +
+
+ {{ Form::label('label2_fields', trans('admin/settings/general.label2_fields')) }} +
+
+ {{ Form::text('label2_fields', old('label2_fields', $setting->label2_fields), [ 'class'=>'form-control', 'aria-label'=>'label2_fields' ]) }} + {!! $errors->first('label2_fields', '') !!} +

{!! trans('admin/settings/general.label2_fields_help') !!}

+

+ {!! trans('admin/settings/general.label2_fields_help_semi') !!}.
+ {!! trans('admin/settings/general.label2_fields_help_pipe') !!}.
+ {!! trans('admin/settings/general.label2_fields_help_once') !!}. +

+
-
-
- - - - - -
-
-
+ @include('partials.bootstrap-table') + + @else + + {{ Form::hidden('label2_template', old('label2_template', $setting->label2_template)) }} + {{ Form::hidden('label2_title', old('label2_title', $setting->label2_title)) }} + {{ Form::hidden('label2_asset_logo', old('label2_asset_logo', $setting->label2_asset_logo)) }} + {{ Form::hidden('label2_1d_type', old('label2_1d_type', $setting->label2_1d_type)) }} + {{ Form::hidden('label2_2d_type', old('label2_2d_type', $setting->label2_2d_type)) }} + {{ Form::hidden('label2_2d_target', old('label2_2d_target', $setting->label2_2d_target)) }} + {{ Form::hidden('label2_fields', old('label2_fields', $setting->label2_fields)) }} + @endif + @if ($setting->label2_enable && ($setting->label2_template != 'DefaultLabel')) + + {{ Form::hidden('labels_per_page', old('labels_per_page', $setting->labels_per_page)) }} + {{ Form::hidden('labels_fontsize', old('labels_fontsize', $setting->labels_fontsize)) }} + {{ Form::hidden('labels_width', old('labels_width', $setting->labels_width)) }} + {{ Form::hidden('labels_height', old('labels_height', $setting->labels_height)) }} + {{ Form::hidden('labels_display_sgutter', old('labels_display_sgutter', $setting->labels_display_sgutter)) }} + {{ Form::hidden('labels_display_bgutter', old('labels_display_bgutter', $setting->labels_display_bgutter)) }} + {{ Form::hidden('labels_pmargin_top', old('labels_pmargin_top', $setting->labels_pmargin_top)) }} + {{ Form::hidden('labels_pmargin_bottom', old('labels_pmargin_bottom', $setting->labels_pmargin_bottom)) }} + {{ Form::hidden('labels_pmargin_left', old('labels_pmargin_left', $setting->labels_pmargin_left)) }} + {{ Form::hidden('labels_pmargin_right', old('labels_pmargin_right', $setting->labels_pmargin_right)) }} + {{ Form::hidden('labels_pagewidth', old('labels_pagewidth', $setting->labels_pagewidth)) }} + {{ Form::hidden('labels_pageheight', old('labels_pageheight', $setting->labels_pageheight)) }} + {{ Form::hidden('labels_display_name', old('labels_display_name', $setting->labels_display_name)) }} + {{ Form::hidden('labels_display_serial', old('labels_display_serial', $setting->labels_display_serial)) }} + {{ Form::hidden('labels_display_tag', old('labels_display_tag', $setting->labels_display_tag)) }} + {{ Form::hidden('labels_display_model', old('labels_display_model', $setting->labels_display_model)) }} + {{ Form::hidden('labels_display_company_name', old('labels_display_company_name', $setting->labels_display_company_name)) }} + @else + +
+
+ {{ Form::label('labels_per_page', trans('admin/settings/general.labels_per_page')) }} +
+
+ {{ Form::text('labels_per_page', old('labels_per_page', $setting->labels_per_page), ['class' => 'form-control','style' => 'width: 100px;', 'aria-label'=>'labels_per_page']) }} + {!! $errors->first('labels_per_page', '') !!} +
+
+ +
+
+ {{ Form::label('labels_fontsize', trans('admin/settings/general.labels_fontsize')) }} +
+
+
+ {{ Form::text('labels_fontsize', old('labels_fontsize', $setting->labels_fontsize), ['class' => 'form-control', 'aria-label'=>'labels_fontsize']) }} +
{{ trans('admin/settings/general.text_pt') }}
+
+
+
+ {!! $errors->first('labels_fontsize', '') !!} +
+
+ +
+
+ {{ Form::label('labels_width', trans('admin/settings/general.label_dimensions')) }} +
+
+
+ {{ Form::text('labels_width', old('labels_width', $setting->labels_width), ['class' => 'form-control', 'aria-label'=>'labels_width']) }} +
{{ trans('admin/settings/general.width_w') }}
+
+
+
+
+ {{ Form::text('labels_height', old('labels_height', $setting->labels_height), ['class' => 'form-control', 'aria-label'=>'labels_height']) }} +
{{ trans('admin/settings/general.height_h') }}
+
+
+
+ {!! $errors->first('labels_width', '') !!} + {!! $errors->first('labels_height', '') !!} +
+
+ +
+
+ {{ Form::label('labels_display_sgutter', trans('admin/settings/general.label_gutters')) }} +
+
+
+ {{ Form::text('labels_display_sgutter', old('labels_display_sgutter', $setting->labels_display_sgutter), ['class' => 'form-control', 'aria-label'=>'labels_display_sgutter']) }} +
{{ trans('admin/settings/general.horizontal') }}
+
+
+
+
+ {{ Form::text('labels_display_bgutter', old('labels_display_bgutter', $setting->labels_display_bgutter), ['class' => 'form-control', 'aria-label'=>'labels_display_bgutter']) }} +
{{ trans('admin/settings/general.vertical') }}
+
+
+
+ {!! $errors->first('labels_display_sgutter', '') !!} + {!! $errors->first('labels_display_bgutter', '') !!} +
+
+ +
+
+ {{ Form::label('labels_pmargin_top', trans('admin/settings/general.page_padding')) }} +
+
+
+ {{ Form::text('labels_pmargin_top', old('labels_pmargin_top', $setting->labels_pmargin_top), ['class' => 'form-control', 'aria-label'=>'labels_pmargin_top']) }} +
{{ trans('admin/settings/general.top') }}
+
+
+ {{ Form::text('labels_pmargin_right', old('labels_pmargin_right', $setting->labels_pmargin_right), ['class' => 'form-control', 'aria-label'=>'labels_pmargin_right']) }} +
{{ trans('admin/settings/general.right') }}
+
+
+
+
+ {{ Form::text('labels_pmargin_bottom', old('labels_pmargin_bottom', $setting->labels_pmargin_bottom), ['class' => 'form-control', 'aria-label'=>'labels_pmargin_bottom']) }} +
{{ trans('admin/settings/general.bottom') }}
+
+
+ {{ Form::text('labels_pmargin_left', old('labels_pmargin_left', $setting->labels_pmargin_left), ['class' => 'form-control', 'aria-label'=>'labels_pmargin_left']) }} +
{{ trans('admin/settings/general.left') }}
+
+ +
+
+ {!! $errors->first('labels_width', '') !!} + {!! $errors->first('labels_height', '') !!} +
+
+ +
+
+ {{ Form::label('labels_pagewidth', trans('admin/settings/general.page_dimensions')) }} +
+
+
+ {{ Form::text('labels_pagewidth', old('labels_pagewidth', $setting->labels_pagewidth), ['class' => 'form-control', 'aria-label'=>'labels_pagewidth']) }} +
{{ trans('admin/settings/general.width_w') }}
+
+
+
+
+ {{ Form::text('labels_pageheight', old('labels_pageheight', $setting->labels_pageheight), ['class' => 'form-control', 'aria-label'=>'labels_pageheight']) }} +
{{ trans('admin/settings/general.height_h') }}
+
+
+
+ {!! $errors->first('labels_pagewidth', '') !!} + {!! $errors->first('labels_pageheight', '') !!} +
+
+ +
+
+ {{ Form::label('labels_display', trans('admin/settings/general.label_fields')) }} +
+
+
+ + + + + +
+
+
+ + @endif
From d37605ff1882482c1d5bcdeb6f5cf27196e5048c Mon Sep 17 00:00:00 2001 From: Cram42 Date: Tue, 1 Nov 2022 20:01:18 +0800 Subject: [PATCH 10/39] Add localization strings --- resources/lang/en/admin/labels/message.php | 11 ++++++++ resources/lang/en/admin/labels/table.php | 12 +++++++++ resources/lang/en/admin/settings/general.php | 27 ++++++++++++++++++++ routes/web/hardware.php | 6 ++++- 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 resources/lang/en/admin/labels/message.php create mode 100644 resources/lang/en/admin/labels/table.php diff --git a/resources/lang/en/admin/labels/message.php b/resources/lang/en/admin/labels/message.php new file mode 100644 index 000000000..96785f075 --- /dev/null +++ b/resources/lang/en/admin/labels/message.php @@ -0,0 +1,11 @@ + 'Invalid count returned from :name. Expected :expected, got :actual.', + 'invalid_return_type' => 'Invalid type returned from :name. Expected :expected, got :actual.', + 'invalid_return_value' => 'Invalid value returned from :name. Expected :expected, got :actual.', + + 'does_not_exist' => 'Label does not exist', + +]; diff --git a/resources/lang/en/admin/labels/table.php b/resources/lang/en/admin/labels/table.php new file mode 100644 index 000000000..bc0ab7a97 --- /dev/null +++ b/resources/lang/en/admin/labels/table.php @@ -0,0 +1,12 @@ + 'Labels', + 'support_fields' => 'Fields', + 'support_1d_barcode' => '1D', + 'support_2d_barcode' => '2D', + 'support_logo' => 'Logo', + 'support_title' => 'Title', + +]; \ No newline at end of file diff --git a/resources/lang/en/admin/settings/general.php b/resources/lang/en/admin/settings/general.php index f5542d505..e969dd93c 100644 --- a/resources/lang/en/admin/settings/general.php +++ b/resources/lang/en/admin/settings/general.php @@ -320,4 +320,31 @@ return [ 'setup_migration_create_user' => 'Next: Create User', 'ldap_settings_link' => 'LDAP Settings Page', 'slack_test' => 'Test Integration', + + 'label2_enable' => 'New Label Engine', + 'label2_enable_help' => 'Switch to the new label engine. Note: You will need to save this setting before setting others.', + 'label2_template' => 'Template', + 'label2_template_help' => 'Select which template to use for label generation', + 'label2_title' => 'Title', + 'label2_title_help' => 'The title to show on labels that support it', + 'label2_title_help_phold' => 'The placeholder :company will be replaced with the asset's company name', + 'label2_asset_logo' => 'Use Asset Logo', + 'label2_asset_logo_help' => 'Use the logo of the asset's assigned company, rather than the value at :setting_name', + 'label2_1d_type' => '1D Barcode Type', + 'label2_1d_type_help' => 'Format for 1D barcodes', + 'label2_2d_type' => '2D Barcode Type', + 'label2_2d_type_help' => 'Format for 2D barcodes', + 'label2_2d_target' => '2D Barcode Target', + 'label2_2d_target_help' => 'The URL the 2D barcode points to when scanned', + 'label2_fields' => 'Fields Definition', + 'label2_fields_help' => 'Fields to show on the label in the format Label=asset_field', + 'label2_fields_help_semi' => 'Use ; to separate fields', + 'label2_fields_help_pipe' => 'Use | to allow multiple options in each field. For example Name=name|Nickname=_snipeit_my_custom_field_2 will use name if a value is set, otherwise it will use _snipeit_my_custom_field_2. This is useful to ensure field order', + 'label2_fields_help_once' => 'Each field will only be selected once per label', + + 'help_asterisk_bold' => 'Text entered as **text** will be displayed as bold', + 'help_blank_to_use' => 'Leave blank to use the value from :setting_name', + 'help_default_will_use' => ':default will use the value from :setting_name', + 'default' => 'Default', + 'none' => 'None', ]; diff --git a/routes/web/hardware.php b/routes/web/hardware.php index 09811d17d..9f2843410 100644 --- a/routes/web/hardware.php +++ b/routes/web/hardware.php @@ -181,4 +181,8 @@ Route::resource('hardware', 'middleware' => ['auth'], 'parameters' => ['asset' => 'asset_id' ], -]); \ No newline at end of file +]); + +Route::get('ht/{any?}', + [AssetsController::class, 'getAssetByTag'] +)->where('any', '.*')->name('ht/assetTag'); \ No newline at end of file From 7b29ddd283b41152e4356d8e82742806581d43ca Mon Sep 17 00:00:00 2001 From: Cram42 Date: Tue, 1 Nov 2022 20:02:12 +0800 Subject: [PATCH 11/39] Tie into tag generation --- app/Http/Controllers/Assets/AssetsController.php | 9 ++++++--- app/Http/Controllers/Assets/BulkAssetsController.php | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php index b0fde82c5..545d874d4 100755 --- a/app/Http/Controllers/Assets/AssetsController.php +++ b/app/Http/Controllers/Assets/AssetsController.php @@ -13,6 +13,7 @@ use App\Models\Company; use App\Models\Location; use App\Models\Setting; use App\Models\User; +use App\View\Label; use Auth; use Carbon\Carbon; use DB; @@ -441,11 +442,12 @@ class AssetsController extends Controller * @since [v3.0] * @return Redirect */ - public function getAssetByTag(Request $request) + public function getAssetByTag($tag=null, Request $request) { + $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); @@ -542,9 +544,10 @@ class AssetsController extends Controller $asset = Asset::find($assetId); $this->authorize('view', $asset); - return view('hardware/labels') + return (new Label()) ->with('assets', collect([ $asset ])) ->with('settings', Setting::getSettings()) + ->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 ca48d91f4..b9a3a39cf 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; @@ -44,7 +45,7 @@ class BulkAssetsController extends Controller if ($request->filled('bulk_actions')) { switch ($request->input('bulk_actions')) { case 'labels': - return view('hardware/labels') + return (new Label) ->with('assets', Asset::find($asset_ids)) ->with('settings', Setting::getSettings()) ->with('bulkedit', true) From cde2ba7720acce2591ae6f46d9000042c67a4af9 Mon Sep 17 00:00:00 2001 From: Cram42 Date: Wed, 2 Nov 2022 09:51:02 +0800 Subject: [PATCH 12/39] Fix: Oops --- resources/views/partials/bootstrap-table.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/partials/bootstrap-table.blade.php b/resources/views/partials/bootstrap-table.blade.php index de888dbe7..9ea2c6a41 100644 --- a/resources/views/partials/bootstrap-table.blade.php +++ b/resources/views/partials/bootstrap-table.blade.php @@ -665,7 +665,7 @@ function labelPerPageFormatter(value, row, index, field) { if (row) { - if (row.sheet_labels) { return 1; } + if (!row.hasOwnProperty('sheet_info')) { return 1; } else { return row.sheet_info.labels_per_page; } } } From 19150aeb4482935ab9e1b4dd7fa4191c8e7c8780 Mon Sep 17 00:00:00 2001 From: Cram42 Date: Wed, 2 Nov 2022 10:09:11 +0800 Subject: [PATCH 13/39] Add P-touch TZe 24mm Tape Example --- app/Models/Labels/Tapes/Brother/TZe_24mm.php | 19 +++++ .../Labels/Tapes/Brother/TZe_24mm_A.php | 69 +++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 app/Models/Labels/Tapes/Brother/TZe_24mm.php create mode 100644 app/Models/Labels/Tapes/Brother/TZe_24mm_A.php 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..babfb5d40 --- /dev/null +++ b/app/Models/Labels/Tapes/Brother/TZe_24mm_A.php @@ -0,0 +1,69 @@ +getPrintableArea(); + + $x = $pa->x1; + $y = $pa->y1; + $w = $pa->w; + + if ($record->has('barcode2d')) { + static::write2DBarcode( + $pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type, + $x, $y, $pa->h, $pa->h + ); + $x += $pa->h + self::BARCODE_MARGIN; + $w -= $pa->h + self::BARCODE_MARGIN; + } + + if ($record->has('title')) { + static::writeText( + $pdf, $record->get('title'), + $x, $y, + 'freesans', '', self::TITLE_SIZE, 'L', + $w, self::TITLE_SIZE, true, 0 + ); + $y += self::TITLE_SIZE + self::TITLE_MARGIN; + } + + foreach ($record->get('fields') as $label => $value) { + static::writeText( + $pdf, $label, + $x, $y, + 'freesans', '', self::LABEL_SIZE, 'L', + $w, self::LABEL_SIZE, true, 0 + ); + $y += self::LABEL_SIZE + self::LABEL_MARGIN; + + static::writeText( + $pdf, $value, + $x, $y, + 'freemono', 'B', self::FIELD_SIZE, 'L', + $w, self::FIELD_SIZE, true, 0, 0.3 + ); + $y += self::FIELD_SIZE + self::FIELD_MARGIN; + } + } +} \ No newline at end of file From a60ee7736b67595dfcf2f5afb74327f51980cb54 Mon Sep 17 00:00:00 2001 From: Cram42 Date: Wed, 2 Nov 2022 12:22:55 +0800 Subject: [PATCH 14/39] Add P-touch TZe 12mm Tape Example --- app/Models/Labels/Tapes/Brother/TZe_12mm.php | 19 +++++++ .../Labels/Tapes/Brother/TZe_12mm_A.php | 54 +++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 app/Models/Labels/Tapes/Brother/TZe_12mm.php create mode 100644 app/Models/Labels/Tapes/Brother/TZe_12mm_A.php 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..060ab3cb8 --- /dev/null +++ b/app/Models/Labels/Tapes/Brother/TZe_12mm_A.php @@ -0,0 +1,54 @@ +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 + ); + } + + $fields = $record->get('fields'); + $y = $pa->y1 + self::BARCODE_SIZE + self::BARCODE_MARGIN; + $realSize = $pa->h - self::BARCODE_SIZE - self::BARCODE_MARGIN; + $fontSize = $realSize + self::FIELD_SIZE_MOD; + + if ($fields->count() >= 1) { + static::writeText( + $pdf, $fields->values()->get(0), + $pa->x1, $y, + 'freemono', 'B', $fontSize, 'L', + $pa->w, $realSize, true, 0, 0.1 + ); + } + if ($fields->count() >= 2) { + static::writeText( + $pdf, $fields->values()->get(1), + $pa->x1, $y, + 'freemono', 'B', $fontSize, 'R', + $pa->w, $realSize, true, 0, 0.1 + ); + } + + } +} \ No newline at end of file From a8b6a4a259d3992ad77cc8de16db1a433879345d Mon Sep 17 00:00:00 2001 From: Cram42 Date: Wed, 2 Nov 2022 16:04:40 +0800 Subject: [PATCH 15/39] Allow passing "template" param --- app/Http/Controllers/Assets/AssetsController.php | 1 + app/View/Label.php | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php index 545d874d4..dd5fb35f5 100755 --- a/app/Http/Controllers/Assets/AssetsController.php +++ b/app/Http/Controllers/Assets/AssetsController.php @@ -547,6 +547,7 @@ class AssetsController extends Controller 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/View/Label.php b/app/View/Label.php index c720f5a8d..c855d359f 100644 --- a/app/View/Label.php +++ b/app/View/Label.php @@ -27,7 +27,6 @@ class Label implements View $this->data = new Collection(); } - /** * Render the PDF label. * @@ -38,9 +37,10 @@ class Label implements View $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) { + if ((!$settings->label2_enable) && (!$template)) { return view('hardware/labels') ->with('assets', $assets) ->with('settings', $settings) @@ -48,8 +48,9 @@ class Label implements View ->with('count', $this->data->get('count')); } + if (empty($template)) $template = LabelModel::find($settings->label2_template); + elseif (is_string($template)) $template = LabelModel::find($template); - $template = LabelModel::find($settings->label2_template); $template->validate(); $pdf = new TCPDF( From 06ce40ac086d3489b7e9104f0fbb3c08f4dfd3d7 Mon Sep 17 00:00:00 2001 From: Cram42 Date: Wed, 2 Nov 2022 16:05:52 +0800 Subject: [PATCH 16/39] Don't add data if it's not there --- app/View/Label.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/View/Label.php b/app/View/Label.php index c855d359f..3ce952a93 100644 --- a/app/View/Label.php +++ b/app/View/Label.php @@ -112,14 +112,14 @@ class Label implements View $assetData->put('tag', $asset->asset_tag); if ($template->getSupportTitle()) { - $assetData->put('title', !empty($settings->label2_title) ? + $title = !empty($settings->label2_title) ? str_ireplace(':company', $asset->company->name, $settings->label2_title) : - $settings->qr_text - ); + $settings->qr_text; + if (!empty($title)) $assetData->put('title', $title); } if ($template->getSupportLogo()) { - $assetData->put('logo', $settings->label2_asset_logo ? + $logo = $settings->label2_asset_logo ? ( !empty($asset->company->image) ? Storage::disk('public')->path('companies/'.e($asset->company->image)) : @@ -129,8 +129,8 @@ class Label implements View !empty($settings->label_logo) ? Storage::disk('public')->path(''.e($settings->label_logo)) : null - ) - ); + ); + if (!empty($logo)) $assetData->put('logo', $logo); } if ($template->getSupport1DBarcode()) { @@ -138,7 +138,7 @@ class Label implements View $barcode1DType = ($barcode1DType == 'default') ? (($settings->alt_barcode_enabled) ? $settings->alt_barcode : null) : $barcode1DType; - if ($barcode1DType) { + if ($barcode1DType != 'none') { $assetData->put('barcode1d', (object)[ 'type' => $barcode1DType, 'content' => $asset->asset_tag, @@ -151,7 +151,7 @@ class Label implements View $barcode2DType = ($barcode2DType == 'default') ? (($settings->qr_code) ? $settings->barcode_type : null) : $barcode2DType; - if ($barcode2DType) { + if ($barcode2DType != 'none') { switch ($settings->label2_2d_target) { case 'ht_tag': $barcode2DTarget = route('ht/assetTag', $asset->asset_tag); break; case 'hardware_id': From 4bb40adfce82bc4a5fb384a8a45c90b1750ee6a2 Mon Sep 17 00:00:00 2001 From: Cram42 Date: Wed, 2 Nov 2022 16:07:50 +0800 Subject: [PATCH 17/39] Add Asset Tag as separate supported feature --- app/Http/Transformers/LabelsTransformer.php | 1 + app/Models/Labels/DefaultLabel.php | 1 + app/Models/Labels/Label.php | 7 ++ app/Models/Labels/Sheets/Avery/L7162_A.php | 100 +++++++++++++++++ app/Models/Labels/Sheets/Avery/L7162_B.php | 103 ++++++++++++++++++ .../Labels/Sheets/Avery/L7162_Example.php | 91 ---------------- .../Labels/Sheets/Avery/L7162_Example_QR.php | 77 ------------- .../Labels/Tapes/Brother/TZe_12mm_A.php | 1 + .../Labels/Tapes/Brother/TZe_24mm_A.php | 1 + app/Presenters/LabelPresenter.php | 8 ++ resources/lang/en/admin/labels/table.php | 1 + 11 files changed, 223 insertions(+), 168 deletions(-) create mode 100644 app/Models/Labels/Sheets/Avery/L7162_A.php create mode 100644 app/Models/Labels/Sheets/Avery/L7162_B.php delete mode 100644 app/Models/Labels/Sheets/Avery/L7162_Example.php delete mode 100644 app/Models/Labels/Sheets/Avery/L7162_Example_QR.php diff --git a/app/Http/Transformers/LabelsTransformer.php b/app/Http/Transformers/LabelsTransformer.php index f575f91d6..8e0e8ca44 100644 --- a/app/Http/Transformers/LabelsTransformer.php +++ b/app/Http/Transformers/LabelsTransformer.php @@ -34,6 +34,7 @@ class LabelsTransformer '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(), diff --git a/app/Models/Labels/DefaultLabel.php b/app/Models/Labels/DefaultLabel.php index 103e3181e..e43519325 100644 --- a/app/Models/Labels/DefaultLabel.php +++ b/app/Models/Labels/DefaultLabel.php @@ -90,6 +90,7 @@ class DefaultLabel extends RectangleSheet 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; } diff --git a/app/Models/Labels/Label.php b/app/Models/Labels/Label.php index 248044163..2ac37abe1 100644 --- a/app/Models/Labels/Label.php +++ b/app/Models/Labels/Label.php @@ -65,6 +65,13 @@ abstract class Label */ public abstract function getMarginRight(); + /** + * Returns whether the template supports an asset tag. + * + * @return bool + */ + public abstract function getSupportAssetTag(); + /** * Returns whether the template supports a 1D barcode. * 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..c6d10cc5f --- /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 $label => $value) { + static::writeText( + $pdf, $label, + $currentX, $currentY, + 'freesans', '', self::LABEL_SIZE, 'L', + $usableWidth, self::LABEL_SIZE, true, 0 + ); + $currentY += self::LABEL_SIZE + self::LABEL_MARGIN; + + static::writeText( + $pdf, $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..8aea715e0 --- /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 $label => $value) { + static::writeText( + $pdf, $label, + $currentX, $currentY, + 'freesans', '', self::LABEL_SIZE, 'L', + $usableWidth, self::LABEL_SIZE, true, 0 + ); + $currentY += self::LABEL_SIZE + self::LABEL_MARGIN; + + static::writeText( + $pdf, $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/L7162_Example.php b/app/Models/Labels/Sheets/Avery/L7162_Example.php deleted file mode 100644 index 10d1a4f9e..000000000 --- a/app/Models/Labels/Sheets/Avery/L7162_Example.php +++ /dev/null @@ -1,91 +0,0 @@ -getLabelPrintableArea(); - - $x = $pa->x1; - $y = $pa->y1; - $w = $pa->w; - - static::write1DBarcode( - $pdf, $record->get('barcode1d')->content, $record->get('barcode1d')->type, - $pa->x1, $pa->y2 - self::BARCODE1D_SIZE, - $pa->w, self::BARCODE1D_SIZE - ); - - if ($record->get('logo')) { - $logoMaxHeight = $pa->h - self::BARCODE1D_SIZE - self::LOGO_MARGIN; - $logoSize = static::writeImage( - $pdf, $record->get('logo'), - $x, $y, - self::LOGO_MAX_WIDTH, $logoMaxHeight, - 'L', 'T', 300, - true, false, 0 - ); - $x += $logoSize[0] + self::LOGO_MARGIN; - $w -= ($logoSize[0] + self::LOGO_MARGIN); - } - - if ($record->get('title')) { - static::writeText( - $pdf, $record->get('title'), - $x, $y, - 'freesans', '', self::TITLE_SIZE, 'L', - $w, self::TITLE_SIZE, true, 0 - ); - $y += self::TITLE_SIZE + self::TITLE_MARGIN; - } - - foreach ($record->get('fields') as $label => $value) { - static::writeText( - $pdf, $label, - $x, $y, - 'freesans', '', self::LABEL_SIZE, 'L', - $w, self::LABEL_SIZE, true, 0 - ); - $y += self::LABEL_SIZE + self::LABEL_MARGIN; - - static::writeText( - $pdf, $value, - $x, $y, - 'freemono', 'B', self::FIELD_SIZE, 'L', - $w, self::FIELD_SIZE, true, 0, 0.3 - ); - $y += self::FIELD_SIZE + self::FIELD_MARGIN; - } - - } -} - - -?> \ No newline at end of file diff --git a/app/Models/Labels/Sheets/Avery/L7162_Example_QR.php b/app/Models/Labels/Sheets/Avery/L7162_Example_QR.php deleted file mode 100644 index 54c886e6e..000000000 --- a/app/Models/Labels/Sheets/Avery/L7162_Example_QR.php +++ /dev/null @@ -1,77 +0,0 @@ -getLabelPrintableArea(); - - static::write2DBarcode( - $pdf, $record->get('barcode2d')->content, $record->get('barcode2d')->type, - $pa->x1, $pa->y1, - $pa->h, $pa->h - ); - - $x = $pa->x1 + $pa->h + self::BARCODE2D_MARGIN; - $y = $pa->y1; - $w = $pa->w - $pa->h - self::BARCODE2D_MARGIN; - - if ($record->get('title')) { - static::writeText( - $pdf, $record->get('title'), - $x, $y, - 'freesans', '', self::TITLE_SIZE, 'L', - $w, self::TITLE_SIZE, true, 0 - ); - $y += self::TITLE_SIZE + self::TITLE_MARGIN; - } - - foreach ($record->get('fields') as $label => $value) { - static::writeText( - $pdf, $label, - $x, $y, - 'freesans', '', self::LABEL_SIZE, 'L', - $w, self::LABEL_SIZE, true, 0 - ); - $y += self::LABEL_SIZE + self::LABEL_MARGIN; - - static::writeText( - $pdf, $value, - $x, $y, - 'freemono', 'B', self::FIELD_SIZE, 'L', - $w, self::FIELD_SIZE, true, 0, 0.3 - ); - $y += self::FIELD_SIZE + self::FIELD_MARGIN; - } - - } -} - - -?> \ 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 index 060ab3cb8..dbac6b9a7 100644 --- a/app/Models/Labels/Tapes/Brother/TZe_12mm_A.php +++ b/app/Models/Labels/Tapes/Brother/TZe_12mm_A.php @@ -10,6 +10,7 @@ class TZe_12mm_A extends TZe_12mm public function getUnit() { return 'mm'; } public function getWidth() { return 50.0; } + public function getSupportAssetTag() { return false; } public function getSupport1DBarcode() { return true; } public function getSupport2DBarcode() { return false; } public function getSupportFields() { return 2; } diff --git a/app/Models/Labels/Tapes/Brother/TZe_24mm_A.php b/app/Models/Labels/Tapes/Brother/TZe_24mm_A.php index babfb5d40..26618c164 100644 --- a/app/Models/Labels/Tapes/Brother/TZe_24mm_A.php +++ b/app/Models/Labels/Tapes/Brother/TZe_24mm_A.php @@ -14,6 +14,7 @@ class TZe_24mm_A extends TZe_24mm public function getUnit() { return 'mm'; } public function getWidth() { return 65.0; } + public function getSupportAssetTag() { return false; } public function getSupport1DBarcode() { return false; } public function getSupport2DBarcode() { return true; } public function getSupportFields() { return 3; } diff --git a/app/Presenters/LabelPresenter.php b/app/Presenters/LabelPresenter.php index 387118432..5ff95d2c4 100644 --- a/app/Presenters/LabelPresenter.php +++ b/app/Presenters/LabelPresenter.php @@ -48,6 +48,14 @@ class LabelPresenter extends Presenter '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, diff --git a/resources/lang/en/admin/labels/table.php b/resources/lang/en/admin/labels/table.php index bc0ab7a97..87dee4bad 100644 --- a/resources/lang/en/admin/labels/table.php +++ b/resources/lang/en/admin/labels/table.php @@ -4,6 +4,7 @@ return [ 'labels_per_page' => 'Labels', 'support_fields' => 'Fields', + 'support_asset_tag' => 'Tag', 'support_1d_barcode' => '1D', 'support_2d_barcode' => '2D', 'support_logo' => 'Logo', From 4fee5ece439057e12a4e39f0fb899272aa5dc577 Mon Sep 17 00:00:00 2001 From: Cram42 Date: Wed, 2 Nov 2022 16:08:10 +0800 Subject: [PATCH 18/39] Support Avery L7163 --- app/Models/Labels/Sheets/Avery/L7163.php | 71 ++++++++++++++++ app/Models/Labels/Sheets/Avery/L7163_A.php | 98 ++++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 app/Models/Labels/Sheets/Avery/L7163.php create mode 100644 app/Models/Labels/Sheets/Avery/L7163_A.php 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..5fc3d03ee --- /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 $label => $value) { + static::writeText( + $pdf, $label, + $currentX, $currentY, + 'freesans', '', self::LABEL_SIZE, 'L', + $usableWidth, self::LABEL_SIZE, true, 0 + ); + $currentY += self::LABEL_SIZE + self::LABEL_MARGIN; + + static::writeText( + $pdf, $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 From fb467d907848fb83ba996ef6eed07fa65bf5cbec Mon Sep 17 00:00:00 2001 From: Cram42 Date: Wed, 2 Nov 2022 17:20:01 +0800 Subject: [PATCH 19/39] Include Asset for advanced users --- app/Models/Labels/DefaultLabel.php | 3 +-- app/View/Label.php | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Models/Labels/DefaultLabel.php b/app/Models/Labels/DefaultLabel.php index e43519325..3a7dd5af1 100644 --- a/app/Models/Labels/DefaultLabel.php +++ b/app/Models/Labels/DefaultLabel.php @@ -3,7 +3,6 @@ namespace App\Models\Labels; use App\Helpers\Helper; -use App\Models\Asset; use App\Models\Setting; class DefaultLabel extends RectangleSheet @@ -101,7 +100,7 @@ class DefaultLabel extends RectangleSheet public function write($pdf, $record) { - $asset = Asset::find($record->get('id')); + $asset = $record->get('asset'); $settings = Setting::getSettings(); $textY = 0; diff --git a/app/View/Label.php b/app/View/Label.php index 3ce952a93..eef17785f 100644 --- a/app/View/Label.php +++ b/app/View/Label.php @@ -108,6 +108,7 @@ class Label implements View $assetData = new Collection(); + $assetData->put('asset', $asset); $assetData->put('id', $asset->id); $assetData->put('tag', $asset->asset_tag); From 48fb4f2439ea45937bfc52b05953bab71f52ae2a Mon Sep 17 00:00:00 2001 From: Cram42 Date: Wed, 2 Nov 2022 17:23:52 +0800 Subject: [PATCH 20/39] Add labels route for testing layouts --- app/Http/Controllers/LabelsController.php | 73 ++++++++++++++++++ public/uploads/companies/.gitignore | 1 + .../uploads/companies/company-image-test.png | Bin 0 -> 59917 bytes routes/web.php | 9 +++ 4 files changed, 83 insertions(+) create mode 100755 app/Http/Controllers/LabelsController.php create mode 100644 public/uploads/companies/company-image-test.png diff --git a/app/Http/Controllers/LabelsController.php b/app/Http/Controllers/LabelsController.php new file mode 100755 index 000000000..99214a5c1 --- /dev/null +++ b/app/Http/Controllers/LabelsController.php @@ -0,0 +1,73 @@ + + * @param string $labelName + * @return \Illuminate\Contracts\View\View + */ + public function show(string $labelName) + { + $this->authorize('view', Label::class); + + $labelName = str_replace('/', '\\', $labelName); + $template = Label::find($labelName); + + $this->authorize('view', $template); + + $testAsset = new Asset(); + + $testAsset->id = 999999; + $testAsset->name = 'AST-AB-CD-1234'; + $testAsset->asset_tag = 'TCA-00001'; + $testAsset->serial = 'SN9876543210'; + + $testAsset->company = new Company(); + $testAsset->company->id = 999999; + $testAsset->company->name = 'Test Company Limited'; + $testAsset->company->image = 'company-image-test.png'; + + $testAsset->assignedto = new User(); + $testAsset->assignedto->id = 999999; + $testAsset->assignedto->first_name = 'Test'; + $testAsset->assignedto->last_name = 'Person'; + $testAsset->assignedto->username = 'Test.Person'; + $testAsset->assignedto->employee_num = '0123456789'; + + $testAsset->model = new AssetModel(); + $testAsset->model->id = 999999; + $testAsset->model->name = 'Test Model'; + $testAsset->model->model_number = 'MDL5678'; + $testAsset->model->manufacturer = new Manufacturer(); + $testAsset->model->manufacturer->id = 999999; + $testAsset->model->manufacturer->name = 'Test Manufacturing Inc.'; + $testAsset->model->category = new Category(); + $testAsset->model->category->id = 999999; + $testAsset->model->category->name = 'Test Category'; + + return (new LabelView()) + ->with('assets', collect([$testAsset])) + ->with('settings', Setting::getSettings()) + ->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/public/uploads/companies/.gitignore b/public/uploads/companies/.gitignore index f935021a8..a24807084 100755 --- a/public/uploads/companies/.gitignore +++ b/public/uploads/companies/.gitignore @@ -1 +1,2 @@ !.gitignore +!company-image-test.png \ No newline at end of file diff --git a/public/uploads/companies/company-image-test.png b/public/uploads/companies/company-image-test.png new file mode 100644 index 0000000000000000000000000000000000000000..681ec5401e42d0a66a3858404c1319dba9cf48b2 GIT binary patch literal 59917 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 mfg?1}&EXEk+)P$&0r)@m4DaeGe|Izh0000 'auth'], function () { 'parameters' => ['category' => 'category_id'], ]); + /* + * Labels + */ + Route::get( + 'labels/{labelName}', + [LabelsController::class, 'show'] + )->where('labelName', '.*')->name('labels.show'); + /* * Locations */ From 36210f1c6ad7cc1c9f9300522f10c8714cc0b60f Mon Sep 17 00:00:00 2001 From: Cram42 Date: Wed, 2 Nov 2022 19:30:45 +0800 Subject: [PATCH 21/39] Allow settings to be overridden in request --- app/Http/Controllers/LabelsController.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/LabelsController.php b/app/Http/Controllers/LabelsController.php index 99214a5c1..60c118546 100755 --- a/app/Http/Controllers/LabelsController.php +++ b/app/Http/Controllers/LabelsController.php @@ -61,9 +61,17 @@ class LabelsController extends Controller $testAsset->model->category->id = 999999; $testAsset->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([$testAsset])) - ->with('settings', Setting::getSettings()) + ->with('settings', $settings) ->with('template', $template) ->with('bulkedit', false) ->with('count', 0); From f5fac50e917d8eae74a644b608e0e60428c31e41 Mon Sep 17 00:00:00 2001 From: Cram42 Date: Wed, 2 Nov 2022 19:31:34 +0800 Subject: [PATCH 22/39] Add preview pane --- resources/views/settings/labels.blade.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/resources/views/settings/labels.blade.php b/resources/views/settings/labels.blade.php index dd9b302b2..1b589b715 100644 --- a/resources/views/settings/labels.blade.php +++ b/resources/views/settings/labels.blade.php @@ -20,8 +20,21 @@ } + - {{ Form::open(['method' => 'POST', 'files' => false, 'autocomplete' => 'off', 'class' => 'form-horizontal', 'role' => 'form' ]) }} + {{ Form::open(['id' => 'settingsForm', 'method' => 'POST', 'files' => false, 'autocomplete' => 'off', 'class' => 'form-horizontal', 'role' => 'form' ]) }} {{csrf_field()}} @@ -60,6 +73,8 @@
{{ Form::label('label2_template', trans('admin/settings/general.label2_template')) }} +
{{ Form::button('Refresh Preview', ['class'=>'btn btn-default btn-sm', 'style'=>'width:100%', 'onClick'=>'refreshPreview()']) }}
+
Date: Wed, 2 Nov 2022 22:38:00 +0800 Subject: [PATCH 23/39] Rework TZe labels for Asset Tag support --- .../Labels/Tapes/Brother/TZe_12mm_A.php | 43 +++++++------- .../Labels/Tapes/Brother/TZe_24mm_A.php | 57 ++++++++++++------- 2 files changed, 59 insertions(+), 41 deletions(-) diff --git a/app/Models/Labels/Tapes/Brother/TZe_12mm_A.php b/app/Models/Labels/Tapes/Brother/TZe_12mm_A.php index dbac6b9a7..786f6eca4 100644 --- a/app/Models/Labels/Tapes/Brother/TZe_12mm_A.php +++ b/app/Models/Labels/Tapes/Brother/TZe_12mm_A.php @@ -4,16 +4,16 @@ namespace App\Models\Labels\Tapes\Brother; class TZe_12mm_A extends TZe_12mm { - private const BARCODE_SIZE = 2.50; + private const BARCODE_SIZE = 3.20; private const BARCODE_MARGIN = 0.30; - private const FIELD_SIZE_MOD = 1.00; + private const TEXT_SIZE_MOD = 1.00; public function getUnit() { return 'mm'; } public function getWidth() { return 50.0; } - public function getSupportAssetTag() { return false; } + public function getSupportAssetTag() { return true; } public function getSupport1DBarcode() { return true; } public function getSupport2DBarcode() { return false; } - public function getSupportFields() { return 2; } + public function getSupportFields() { return 1; } public function getSupportLogo() { return false; } public function getSupportTitle() { return false; } @@ -28,26 +28,27 @@ class TZe_12mm_A extends TZe_12mm $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; - $fields = $record->get('fields'); - $y = $pa->y1 + self::BARCODE_SIZE + self::BARCODE_MARGIN; - $realSize = $pa->h - self::BARCODE_SIZE - self::BARCODE_MARGIN; - $fontSize = $realSize + self::FIELD_SIZE_MOD; + $tagWidth = $pa->w / 3; + $fieldWidth = $pa->w / 3 * 2; - if ($fields->count() >= 1) { + 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, $fields->values()->get(0), - $pa->x1, $y, - 'freemono', 'B', $fontSize, 'L', - $pa->w, $realSize, true, 0, 0.1 - ); - } - if ($fields->count() >= 2) { - static::writeText( - $pdf, $fields->values()->get(1), - $pa->x1, $y, - 'freemono', 'B', $fontSize, 'R', - $pa->w, $realSize, true, 0, 0.1 + $pdf, $record->get('fields')->values()->get(0), + $pa->x1 + ($tagWidth), $currentY, + 'freemono', 'b', $fontSize, 'R', + $fieldWidth, $usableHeight, true, 0, 0 ); } diff --git a/app/Models/Labels/Tapes/Brother/TZe_24mm_A.php b/app/Models/Labels/Tapes/Brother/TZe_24mm_A.php index 26618c164..36d185d64 100644 --- a/app/Models/Labels/Tapes/Brother/TZe_24mm_A.php +++ b/app/Models/Labels/Tapes/Brother/TZe_24mm_A.php @@ -5,16 +5,17 @@ namespace App\Models\Labels\Tapes\Brother; class TZe_24mm_A extends TZe_24mm { private const BARCODE_MARGIN = 1.40; + private const TAG_SIZE = 2.80; private const TITLE_SIZE = 2.80; - private const TITLE_MARGIN = 0.60; - private const LABEL_SIZE = 1.90; - private const LABEL_MARGIN = - 0.30; + private const TITLE_MARGIN = 0.50; + private const LABEL_SIZE = 2.00; + private const LABEL_MARGIN = - 0.35; private const FIELD_SIZE = 3.20; - private const FIELD_MARGIN = 0.30; + private const FIELD_MARGIN = 0.15; public function getUnit() { return 'mm'; } public function getWidth() { return 65.0; } - public function getSupportAssetTag() { return false; } + public function getSupportAssetTag() { return true; } public function getSupport1DBarcode() { return false; } public function getSupport2DBarcode() { return true; } public function getSupportFields() { return 3; } @@ -26,45 +27,61 @@ class TZe_24mm_A extends TZe_24mm public function write($pdf, $record) { $pa = $this->getPrintableArea(); - $x = $pa->x1; - $y = $pa->y1; - $w = $pa->w; + $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, - $x, $y, $pa->h, $pa->h + $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 ); - $x += $pa->h + self::BARCODE_MARGIN; - $w -= $pa->h + self::BARCODE_MARGIN; } if ($record->has('title')) { static::writeText( $pdf, $record->get('title'), - $x, $y, + $currentX, $currentY, 'freesans', '', self::TITLE_SIZE, 'L', - $w, self::TITLE_SIZE, true, 0 + $usableWidth, self::TITLE_SIZE, true, 0 ); - $y += self::TITLE_SIZE + self::TITLE_MARGIN; + $currentY += self::TITLE_SIZE + self::TITLE_MARGIN; } foreach ($record->get('fields') as $label => $value) { static::writeText( $pdf, $label, - $x, $y, + $currentX, $currentY, 'freesans', '', self::LABEL_SIZE, 'L', - $w, self::LABEL_SIZE, true, 0 + $usableWidth, self::LABEL_SIZE, true, 0, 0 ); - $y += self::LABEL_SIZE + self::LABEL_MARGIN; + $currentY += self::LABEL_SIZE + self::LABEL_MARGIN; static::writeText( $pdf, $value, - $x, $y, + $currentX, $currentY, 'freemono', 'B', self::FIELD_SIZE, 'L', - $w, self::FIELD_SIZE, true, 0, 0.3 + $usableWidth, self::FIELD_SIZE, true, 0, 0.3 ); - $y += self::FIELD_SIZE + self::FIELD_MARGIN; + $currentY += self::FIELD_SIZE + self::FIELD_MARGIN; } } } \ No newline at end of file From 3d470d6f2faa204cd1819c7c9ef93a0868871ac6 Mon Sep 17 00:00:00 2001 From: Cram42 Date: Wed, 2 Nov 2022 22:39:08 +0800 Subject: [PATCH 24/39] Drop asset tag from default field def. --- database/migrations/2022_10_25_215520_add_label2_settings.php | 1 - 1 file changed, 1 deletion(-) diff --git a/database/migrations/2022_10_25_215520_add_label2_settings.php b/database/migrations/2022_10_25_215520_add_label2_settings.php index 66b0a0871..692e8440d 100644 --- a/database/migrations/2022_10_25_215520_add_label2_settings.php +++ b/database/migrations/2022_10_25_215520_add_label2_settings.php @@ -22,7 +22,6 @@ class AddLabel2Settings extends Migration $table->string('label2_2d_type')->default('default'); $table->string('label2_2d_target')->default('hardware_id'); $table->string('label2_fields')->default( - trans('admin/hardware/form.tag').'=asset_tag;'. trans('admin/hardware/form.name').'=name;'. trans('admin/hardware/form.serial').'=serial;'. trans('admin/hardware/form.model').'=model.name;' From fd9616683c80e392c9dc06c04933b03796555965 Mon Sep 17 00:00:00 2001 From: Cram42 Date: Thu, 3 Nov 2022 10:20:12 +0800 Subject: [PATCH 25/39] Label preview auto-refresh --- resources/views/settings/labels.blade.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/resources/views/settings/labels.blade.php b/resources/views/settings/labels.blade.php index 1b589b715..a6163a7b1 100644 --- a/resources/views/settings/labels.blade.php +++ b/resources/views/settings/labels.blade.php @@ -29,9 +29,18 @@ .map((value, index, all) => ({[value.name]: value.value}))) }; var params = $.param(settingsOverride); - var url = "{{ route('labels.show', ['labelName' => ':label']) }}".replace(':label', settingsOverride.settings.label2_template) + "?" + params; + var template = settingsOverride.settings.label2_template; + if (template === undefined) return; + + var url = "{{ route('labels.show', ['labelName' => ':label']) }}".replace(':label', template) + "?" + params + "#toolbar=0"; $('iframe#framePreview').attr('src', url); } + + window.addEventListener('load', () => { + $('#settingsForm').on('change', refreshPreview); + $('#settingsForm').on('load-success.bs.table', refreshPreview); + }); + {{ Form::open(['id' => 'settingsForm', 'method' => 'POST', 'files' => false, 'autocomplete' => 'off', 'class' => 'form-horizontal', 'role' => 'form' ]) }} From f849fcca894f1ee37e51243a45ce65797bbd0ae4 Mon Sep 17 00:00:00 2001 From: Cram42 Date: Thu, 10 Nov 2022 18:54:02 +0800 Subject: [PATCH 26/39] Change the way fields are passed --- app/Models/Labels/Field.php | 39 +++++++++++ app/Models/Labels/FieldOption.php | 49 ++++++++++++++ app/Models/Labels/Sheets/Avery/L7162_A.php | 6 +- app/Models/Labels/Sheets/Avery/L7162_B.php | 6 +- app/Models/Labels/Sheets/Avery/L7163_A.php | 6 +- .../Labels/Tapes/Brother/TZe_12mm_A.php | 2 +- .../Labels/Tapes/Brother/TZe_24mm_A.php | 6 +- app/View/Label.php | 64 +++++-------------- 8 files changed, 116 insertions(+), 62 deletions(-) create mode 100644 app/Models/Labels/Field.php create mode 100644 app/Models/Labels/FieldOption.php 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/Sheets/Avery/L7162_A.php b/app/Models/Labels/Sheets/Avery/L7162_A.php index c6d10cc5f..0b3312ba7 100644 --- a/app/Models/Labels/Sheets/Avery/L7162_A.php +++ b/app/Models/Labels/Sheets/Avery/L7162_A.php @@ -75,9 +75,9 @@ class L7162_A extends L7162 $currentY += self::TITLE_SIZE + self::TITLE_MARGIN; } - foreach ($record->get('fields') as $label => $value) { + foreach ($record->get('fields') as $field) { static::writeText( - $pdf, $label, + $pdf, $field['label'], $currentX, $currentY, 'freesans', '', self::LABEL_SIZE, 'L', $usableWidth, self::LABEL_SIZE, true, 0 @@ -85,7 +85,7 @@ class L7162_A extends L7162 $currentY += self::LABEL_SIZE + self::LABEL_MARGIN; static::writeText( - $pdf, $value, + $pdf, $field['value'], $currentX, $currentY, 'freemono', 'B', self::FIELD_SIZE, 'L', $usableWidth, self::FIELD_SIZE, true, 0, 0.3 diff --git a/app/Models/Labels/Sheets/Avery/L7162_B.php b/app/Models/Labels/Sheets/Avery/L7162_B.php index 8aea715e0..268754e04 100644 --- a/app/Models/Labels/Sheets/Avery/L7162_B.php +++ b/app/Models/Labels/Sheets/Avery/L7162_B.php @@ -71,9 +71,9 @@ class L7162_B extends L7162 $currentY += self::TITLE_SIZE + self::TITLE_MARGIN; } - foreach ($record->get('fields') as $label => $value) { + foreach ($record->get('fields') as $field) { static::writeText( - $pdf, $label, + $pdf, $field['label'], $currentX, $currentY, 'freesans', '', self::LABEL_SIZE, 'L', $usableWidth, self::LABEL_SIZE, true, 0 @@ -81,7 +81,7 @@ class L7162_B extends L7162 $currentY += self::LABEL_SIZE + self::LABEL_MARGIN; static::writeText( - $pdf, $value, + $pdf, $field['value'], $currentX, $currentY, 'freemono', 'B', self::FIELD_SIZE, 'L', $usableWidth, self::FIELD_SIZE, true, 0, 0.3 diff --git a/app/Models/Labels/Sheets/Avery/L7163_A.php b/app/Models/Labels/Sheets/Avery/L7163_A.php index 5fc3d03ee..6dc33f64d 100644 --- a/app/Models/Labels/Sheets/Avery/L7163_A.php +++ b/app/Models/Labels/Sheets/Avery/L7163_A.php @@ -73,9 +73,9 @@ class L7163_A extends L7163 ); } - foreach ($record->get('fields') as $label => $value) { + foreach ($record->get('fields') as $field) { static::writeText( - $pdf, $label, + $pdf, $field['label'], $currentX, $currentY, 'freesans', '', self::LABEL_SIZE, 'L', $usableWidth, self::LABEL_SIZE, true, 0 @@ -83,7 +83,7 @@ class L7163_A extends L7163 $currentY += self::LABEL_SIZE + self::LABEL_MARGIN; static::writeText( - $pdf, $value, + $pdf, $field['value'], $currentX, $currentY, 'freemono', 'B', self::FIELD_SIZE, 'L', $usableWidth, self::FIELD_SIZE, true, 0, 0.5 diff --git a/app/Models/Labels/Tapes/Brother/TZe_12mm_A.php b/app/Models/Labels/Tapes/Brother/TZe_12mm_A.php index 786f6eca4..f89cfc5d4 100644 --- a/app/Models/Labels/Tapes/Brother/TZe_12mm_A.php +++ b/app/Models/Labels/Tapes/Brother/TZe_12mm_A.php @@ -45,7 +45,7 @@ class TZe_12mm_A extends TZe_12mm if ($record->get('fields')->count() >= 1) { static::writeText( - $pdf, $record->get('fields')->values()->get(0), + $pdf, $record->get('fields')->values()->get(0)['value'], $pa->x1 + ($tagWidth), $currentY, 'freemono', 'b', $fontSize, 'R', $fieldWidth, $usableHeight, true, 0, 0 diff --git a/app/Models/Labels/Tapes/Brother/TZe_24mm_A.php b/app/Models/Labels/Tapes/Brother/TZe_24mm_A.php index 36d185d64..ea4c6c9df 100644 --- a/app/Models/Labels/Tapes/Brother/TZe_24mm_A.php +++ b/app/Models/Labels/Tapes/Brother/TZe_24mm_A.php @@ -66,9 +66,9 @@ class TZe_24mm_A extends TZe_24mm $currentY += self::TITLE_SIZE + self::TITLE_MARGIN; } - foreach ($record->get('fields') as $label => $value) { + foreach ($record->get('fields') as $field) { static::writeText( - $pdf, $label, + $pdf, $field['label'], $currentX, $currentY, 'freesans', '', self::LABEL_SIZE, 'L', $usableWidth, self::LABEL_SIZE, true, 0, 0 @@ -76,7 +76,7 @@ class TZe_24mm_A extends TZe_24mm $currentY += self::LABEL_SIZE + self::LABEL_MARGIN; static::writeText( - $pdf, $value, + $pdf, $field['value'], $currentX, $currentY, 'freemono', 'B', self::FIELD_SIZE, 'L', $usableWidth, self::FIELD_SIZE, true, 0, 0.3 diff --git a/app/View/Label.php b/app/View/Label.php index eef17785f..67742d88e 100644 --- a/app/View/Label.php +++ b/app/View/Label.php @@ -2,6 +2,7 @@ namespace App\View; +use App\Models\Labels\Field; use App\Models\Labels\Label as LabelModel; use App\Models\Labels\Sheet; use Illuminate\Contracts\View\View; @@ -70,37 +71,10 @@ class Label implements View $pdf->SetSubject('Asset Labels'); $template->preparePDF($pdf); - // 'Label1=field1|Alt1=altfield1;Label2=field2;Alt1=altfield1|Label3=field3' - $fieldDefinitions = (new Collection()) - ->merge(explode(';', $settings->label2_fields)) - ->filter(function ($defString) { - return strpos($defString, '=') !== false; - }) - ->map(function ($defString) { - return (new Collection()) - ->merge(explode('|', $defString)) // ['Label1=field1', 'Alt1=altfield1'] - ->mapWithKeys(function ($altString) { - $parts = explode('=', $altString); - if (count($parts) != 2) throw new \Exception(var_export($parts, true)); - return [ $parts[0] => $parts[1] ]; - }); - }); - /* - $fieldDefinitions should now look like: - [ - [ - 'Label1'=>'field1', - 'Alt1'=>'altfield1' - ], - [ - 'Label2'=>'field2' - ], - [ - 'Alt1'=>'altfield1', - 'Label3'=>'field3' - ] - ] - */ + // 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 @@ -166,25 +140,17 @@ class Label implements View } $fields = $fieldDefinitions - ->map(function ($group, $index) use ($asset) { - return $group->mapWithKeys(function ($definition, $label) use ($asset) { - $value = collect(explode('.', $definition)) - ->reduce(function ($carry, $chunk) { - return $carry ? $carry->{$chunk} : ${$carry}; - }, $asset); - return [ $label => $value ]; - }); - }) - ->reduce(function ($carry, $group, $index) { - $values = $group - ->filter(function ($value, $label) use ($carry) { - if (empty($value)) return false; - if ($carry->has($label)) return false; - return true; - }) - ->take(1); - return $carry->merge($values); + ->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; From a4b93d4bbd8b2f4729b79a7d598ce3f233c578d1 Mon Sep 17 00:00:00 2001 From: Cram42 Date: Thu, 10 Nov 2022 19:20:18 +0800 Subject: [PATCH 27/39] Create Field Definitions helper control --- resources/lang/en/admin/settings/general.php | 7 +- .../label2-field-definitions.blade.php | 337 ++++++++++++++++++ resources/views/settings/labels.blade.php | 12 +- 3 files changed, 343 insertions(+), 13 deletions(-) create mode 100644 resources/views/partials/label2-field-definitions.blade.php diff --git a/resources/lang/en/admin/settings/general.php b/resources/lang/en/admin/settings/general.php index e969dd93c..fe69275c1 100644 --- a/resources/lang/en/admin/settings/general.php +++ b/resources/lang/en/admin/settings/general.php @@ -336,11 +336,8 @@ return [ 'label2_2d_type_help' => 'Format for 2D barcodes', 'label2_2d_target' => '2D Barcode Target', 'label2_2d_target_help' => 'The URL the 2D barcode points to when scanned', - 'label2_fields' => 'Fields Definition', - 'label2_fields_help' => 'Fields to show on the label in the format Label=asset_field', - 'label2_fields_help_semi' => 'Use ; to separate fields', - 'label2_fields_help_pipe' => 'Use | to allow multiple options in each field. For example Name=name|Nickname=_snipeit_my_custom_field_2 will use name if a value is set, otherwise it will use _snipeit_my_custom_field_2. This is useful to ensure field order', - 'label2_fields_help_once' => 'Each field will only be selected once per label', + 'label2_fields' => 'Field Definitions', + 'label2_fields_help' => 'Fields can be added, removed, and reordered in the left column. For each field, multiple options for Label and DataSource can be added, removed, and reordered in the right column.', 'help_asterisk_bold' => 'Text entered as **text** will be displayed as bold', 'help_blank_to_use' => 'Leave blank to use the value from :setting_name', diff --git a/resources/views/partials/label2-field-definitions.blade.php b/resources/views/partials/label2-field-definitions.blade.php new file mode 100644 index 000000000..ebd29ffe0 --- /dev/null +++ b/resources/views/partials/label2-field-definitions.blade.php @@ -0,0 +1,337 @@ +@once + @push('js') + + @endpush + @push('css') + + @endpush +@endonce + +@push('js') + +@endpush +@php + $selector = '[x-data="'.$name.'"]'; +@endphp +@push('css') + +@endpush + +
+ +
+

Fields

+
+ +
+
+ + + + +
+ +

Options

+
+ + +
+
+ + + + +
+
+
\ No newline at end of file diff --git a/resources/views/settings/labels.blade.php b/resources/views/settings/labels.blade.php index a6163a7b1..1209f6df7 100644 --- a/resources/views/settings/labels.blade.php +++ b/resources/views/settings/labels.blade.php @@ -21,6 +21,7 @@ + @@ -957,8 +958,7 @@ @endif - - @livewireScripts + diff --git a/resources/views/partials/label2-field-definitions.blade.php b/resources/views/partials/label2-field-definitions.blade.php index 845cb9125..6522b951d 100644 --- a/resources/views/partials/label2-field-definitions.blade.php +++ b/resources/views/partials/label2-field-definitions.blade.php @@ -1,7 +1,4 @@ @once - @push('js') - - @endpush @push('css') + @endpush +@endonce + +@push('js') + +@endpush + +
+ + +
diff --git a/resources/views/settings/labels.blade.php b/resources/views/settings/labels.blade.php index 1209f6df7..63bf66057 100644 --- a/resources/views/settings/labels.blade.php +++ b/resources/views/settings/labels.blade.php @@ -20,30 +20,6 @@ } - - {{ Form::open(['id' => 'settingsForm', 'method' => 'POST', 'files' => false, 'autocomplete' => 'off', 'class' => 'form-horizontal', 'role' => 'form' ]) }} {{csrf_field()}} @@ -83,8 +59,7 @@
{{ Form::label('label2_template', trans('admin/settings/general.label2_template')) }} -
{{ Form::button('Refresh Preview', ['class'=>'btn btn-default btn-sm', 'style'=>'width:100%', 'onClick'=>'refreshPreview()']) }}
-
+ @include('partials.label2-preview')
Date: Fri, 11 Nov 2022 14:23:38 +0800 Subject: [PATCH 31/39] Fix form refresh after bootstrap-table --- resources/views/settings/labels.blade.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/resources/views/settings/labels.blade.php b/resources/views/settings/labels.blade.php index 63bf66057..5b398c8d6 100644 --- a/resources/views/settings/labels.blade.php +++ b/resources/views/settings/labels.blade.php @@ -82,6 +82,14 @@ id="label2TemplateTable" class="table table-striped snipe-table" >
+
From 60ef5686ab272c51d2a646de4c91d5803f3246d3 Mon Sep 17 00:00:00 2001 From: Cram42 Date: Sat, 12 Nov 2022 19:02:38 +0800 Subject: [PATCH 32/39] Add "Pop Out" button for preview --- .../views/partials/label2-preview.blade.php | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/resources/views/partials/label2-preview.blade.php b/resources/views/partials/label2-preview.blade.php index 7b826f6d4..36617bcc3 100644 --- a/resources/views/partials/label2-preview.blade.php +++ b/resources/views/partials/label2-preview.blade.php @@ -18,12 +18,23 @@ flex-direction: column; } - .l2p-root > label { + .l2p-root > .l2p-top { + display: flex; + flex-direction: row; + align-items: end; + } + + .l2p-root > .l2p-top > label { + flex: 1; font-size: 0.9em; padding: 0; margin: 0; } + .l2p-root > .l2p-top > .l2p-pop-button { + padding: 3px 6px; + } + .l2p-root > iframe { flex: 1; overflow: auto; @@ -63,7 +74,15 @@ .concat('?', $.param(params), '#toolbar=0'); }, - previewURL: '' + _previewURL: '', + get previewURL() { return this._previewURL; }, + set previewURL(url) { + this._previewURL = url; + if (this._popped) this._popped.location = this.previewURL; + }, + + _popped: null, + popout: function() { this._popped = window.open(this.previewURL); } })); }); @@ -71,6 +90,9 @@ @endpush
- +
+ + +
From ffce6ec327112dbf7f1408f8788d0fd2537dfa17 Mon Sep 17 00:00:00 2001 From: Cram42 Date: Sat, 12 Nov 2022 20:29:34 +0800 Subject: [PATCH 33/39] Need uppercase --- app/Models/Labels/Label.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/Labels/Label.php b/app/Models/Labels/Label.php index 2ac37abe1..ff759ac54 100644 --- a/app/Models/Labels/Label.php +++ b/app/Models/Labels/Label.php @@ -534,7 +534,7 @@ abstract class Label * @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($format)) + $size = collect(TCPDF_STATIC::getPageSizeFromFormat(strtoupper($format))) ->sort() ->map(function ($value) use ($unit) { return Helper::convertUnit($value, 'pt', $unit); From 17e81af4cd02a82ba399b3745ec42b605e046139 Mon Sep 17 00:00:00 2001 From: Cram42 Date: Sat, 12 Nov 2022 20:29:50 +0800 Subject: [PATCH 34/39] Add more Avery label sheets --- app/Models/Labels/Sheets/Avery/_5267.php | 71 ++++++++++++++++++ app/Models/Labels/Sheets/Avery/_5267_A.php | 68 +++++++++++++++++ app/Models/Labels/Sheets/Avery/_5520.php | 71 ++++++++++++++++++ app/Models/Labels/Sheets/Avery/_5520_A.php | 85 ++++++++++++++++++++++ 4 files changed, 295 insertions(+) create mode 100644 app/Models/Labels/Sheets/Avery/_5267.php create mode 100644 app/Models/Labels/Sheets/Avery/_5267_A.php create mode 100644 app/Models/Labels/Sheets/Avery/_5520.php create mode 100644 app/Models/Labels/Sheets/Avery/_5520_A.php 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 From b72c6b7afc408f5c7360d1ba9b90cd81d5699310 Mon Sep 17 00:00:00 2001 From: cram42 Date: Wed, 1 Feb 2023 17:48:39 +1100 Subject: [PATCH 35/39] Fix 2D barcode defaults --- app/View/Label.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/View/Label.php b/app/View/Label.php index 67742d88e..de955afff 100644 --- a/app/View/Label.php +++ b/app/View/Label.php @@ -124,9 +124,9 @@ class Label implements View if ($template->getSupport2DBarcode()) { $barcode2DType = $settings->label2_2d_type; $barcode2DType = ($barcode2DType == 'default') ? - (($settings->qr_code) ? $settings->barcode_type : null) : + $settings->barcode_type : $barcode2DType; - if ($barcode2DType != 'none') { + 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': From 4b4c228f1ad32f3558442491265640c6c7d77fc6 Mon Sep 17 00:00:00 2001 From: cram42 Date: Wed, 1 Feb 2023 17:56:23 +1100 Subject: [PATCH 36/39] Correct parameter order --- app/Http/Controllers/Assets/AssetsController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Assets/AssetsController.php b/app/Http/Controllers/Assets/AssetsController.php index dd5fb35f5..e6305b940 100755 --- a/app/Http/Controllers/Assets/AssetsController.php +++ b/app/Http/Controllers/Assets/AssetsController.php @@ -442,7 +442,7 @@ class AssetsController extends Controller * @since [v3.0] * @return Redirect */ - public function getAssetByTag($tag=null, Request $request) + public function getAssetByTag(Request $request, $tag=null) { $tag = $tag ? $tag : $request->get('assetTag'); $topsearch = ($request->get('topsearch') == 'true'); From 73fd0a24ca751598e9b6e37c1810f3dcc043bd16 Mon Sep 17 00:00:00 2001 From: cram42 Date: Wed, 1 Feb 2023 17:58:58 +1100 Subject: [PATCH 37/39] Clarify example asset variable --- app/Http/Controllers/LabelsController.php | 56 +++++++++++------------ 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/app/Http/Controllers/LabelsController.php b/app/Http/Controllers/LabelsController.php index 60c118546..97608cb5e 100755 --- a/app/Http/Controllers/LabelsController.php +++ b/app/Http/Controllers/LabelsController.php @@ -24,42 +24,38 @@ class LabelsController extends Controller */ public function show(string $labelName) { - $this->authorize('view', Label::class); - $labelName = str_replace('/', '\\', $labelName); $template = Label::find($labelName); - $this->authorize('view', $template); + $exampleAsset = new Asset(); - $testAsset = new Asset(); + $exampleAsset->id = 999999; + $exampleAsset->name = 'AST-AB-CD-1234'; + $exampleAsset->asset_tag = 'TCA-00001'; + $exampleAsset->serial = 'SN9876543210'; - $testAsset->id = 999999; - $testAsset->name = 'AST-AB-CD-1234'; - $testAsset->asset_tag = 'TCA-00001'; - $testAsset->serial = 'SN9876543210'; + $exampleAsset->company = new Company(); + $exampleAsset->company->id = 999999; + $exampleAsset->company->name = 'Test Company Limited'; + $exampleAsset->company->image = 'company-image-test.png'; - $testAsset->company = new Company(); - $testAsset->company->id = 999999; - $testAsset->company->name = 'Test Company Limited'; - $testAsset->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'; - $testAsset->assignedto = new User(); - $testAsset->assignedto->id = 999999; - $testAsset->assignedto->first_name = 'Test'; - $testAsset->assignedto->last_name = 'Person'; - $testAsset->assignedto->username = 'Test.Person'; - $testAsset->assignedto->employee_num = '0123456789'; - - $testAsset->model = new AssetModel(); - $testAsset->model->id = 999999; - $testAsset->model->name = 'Test Model'; - $testAsset->model->model_number = 'MDL5678'; - $testAsset->model->manufacturer = new Manufacturer(); - $testAsset->model->manufacturer->id = 999999; - $testAsset->model->manufacturer->name = 'Test Manufacturing Inc.'; - $testAsset->model->category = new Category(); - $testAsset->model->category->id = 999999; - $testAsset->model->category->name = 'Test Category'; + $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')) { @@ -70,7 +66,7 @@ class LabelsController extends Controller } return (new LabelView()) - ->with('assets', collect([$testAsset])) + ->with('assets', collect([$exampleAsset])) ->with('settings', $settings) ->with('template', $template) ->with('bulkedit', false) From cebf0e0de1503f91f6fef4fdfd649c717b2c9e16 Mon Sep 17 00:00:00 2001 From: snipe Date: Tue, 15 Aug 2023 18:07:56 +0100 Subject: [PATCH 38/39] Add @cram42 as a contributor --- .all-contributorsrc | 9 +++++++++ README.md | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) 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 @@ ![Build Status](https://app.chipperci.com/projects/0e5f8979-31eb-4ee6-9abf-050b76ab0383/status/master) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/snipe-it/localized.svg)](https://crowdin.com/project/snipe-it) [![Docker Pulls](https://img.shields.io/docker/pulls/snipe/snipe-it.svg)](https://hub.docker.com/r/snipe/snipe-it/) [![Twitter Follow](https://img.shields.io/twitter/follow/snipeitapp.svg?style=social)](https://twitter.com/snipeitapp) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/553ce52037fc43ea99149785afcfe641)](https://www.codacy.com/app/snipe/snipe-it?utm_source=github.com&utm_medium=referral&utm_content=snipe/snipe-it&utm_campaign=Badge_Grade) -[![All Contributors](https://img.shields.io/badge/all_contributors-325-orange.svg?style=flat-square)](#contributors) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/yZFtShAcKk) [![huntr](https://cdn.huntr.dev/huntr_security_badge_mono.svg)](https://huntr.dev) +[![All Contributors](https://img.shields.io/badge/all_contributors-326-orange.svg?style=flat-square)](#contributors) [![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/yZFtShAcKk) [![huntr](https://cdn.huntr.dev/huntr_security_badge_mono.svg)](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! From 443adc50da0b760a9dfd69fcd477b2947ec27e17 Mon Sep 17 00:00:00 2001 From: snipe Date: Tue, 15 Aug 2023 18:12:25 +0100 Subject: [PATCH 39/39] Fixed unclosed brace Signed-off-by: snipe --- app/Helpers/Helper.php | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/app/Helpers/Helper.php b/app/Helpers/Helper.php index b4c8f5605..680c7acfd 100644 --- a/app/Helpers/Helper.php +++ b/app/Helpers/Helper.php @@ -1253,16 +1253,25 @@ class Helper */ 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'); + 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; + return false; + } }