diff --git a/.gitignore b/.gitignore index b573bec..78affe7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .session_secret public/files/* !public/files/.gitkeep +data/labels/* +!data/labels/.gitkeep diff --git a/app.rb b/app.rb index 55193c4..52ad58b 100644 --- a/app.rb +++ b/app.rb @@ -4,6 +4,7 @@ require "sinatra/reloader" require 'tmpdir' require 'net/http' require_relative 'lib/receipt_builder' +require_relative 'lib/label_repository' set :protection, :except => :frame_options set :environment, :production @@ -24,6 +25,8 @@ TEST_LABEL_PDF = File.join(File.dirname(__FILE__), 'test-label.pdf') PRINTER_DEVICE_EPSON = '/dev/usb/lp_epson' # see /etc/udev/rules.d/99-epson.rules +LABEL_REPO = LabelRepository.new(File.join(File.dirname(__FILE__), 'data', 'labels')) + BROTHER_PRINTERS = { 'ql560' => { port: 8560, label: 'Brother QL-560' }, 'ql700' => { port: 8700, label: 'Brother QL-700' }, @@ -104,6 +107,16 @@ helpers do receipt.print!(PRINTER_DEVICE_EPSON) end + # Prints already-formatted ESC/POS text as-is (just the reset + code page + # prefix and a trailing cut) -- for content prepared elsewhere or reprinted + # from the label repository, unlike print_receipt's title/body template. + def print_raw_escpos(content) + receipt = ReceiptBuilder.new + receipt.align(:left).text(content) + receipt.feed(3).cut + receipt.print!(PRINTER_DEVICE_EPSON) + end + # Transparently reverse-proxies the current request to one of the # brother_ql_web instances, so all three printers (web UI and API alike) # are reachable through this single sinatra port instead of their own @@ -153,9 +166,26 @@ get '/' do end get '/upload' do + @labels = LABEL_REPO.all(type: 'zpl') erb :upload end +post '/shipping/zpl' do + zpl = params[:zpl].to_s + if zpl.strip.empty? + flash 'ZPL text is empty', 'warning' + else + LABEL_REPO.save(name: params[:save_as], type: 'zpl', content: zpl) unless params[:save_as].to_s.empty? + begin + print_to_datamax(zpl) + flash 'ZPL sent to printer', 'success' + rescue => e + flash "ZPL print failed (#{e.message})", 'danger' + end + end + redirect '/upload' +end + post '/upload' do if params[:file] @@ -204,6 +234,7 @@ post '/test-print' do end get '/epson' do + @labels = LABEL_REPO.all(type: 'escpos') erb :epson end @@ -217,6 +248,49 @@ post '/epson/print' do redirect '/epson' end +post '/epson/raw' do + content = params[:content].to_s + if content.strip.empty? + flash 'Receipt text is empty', 'warning' + else + LABEL_REPO.save(name: params[:save_as], type: 'escpos', content: content) unless params[:save_as].to_s.empty? + begin + print_raw_escpos(content) + flash 'Sent to Epson printer', 'success' + rescue => e + flash "Epson print failed (#{e.message})", 'danger' + end + end + redirect '/epson' +end + +post '/labels/:id/print' do + label = LABEL_REPO.find(params[:id]) + halt 404, 'unknown label' unless label + + begin + case label['type'] + when 'zpl' + print_to_datamax(label['content']) + when 'escpos' + print_raw_escpos(label['content']) + end + flash "Printed '#{label['name']}'", 'success' + rescue => e + flash "Print failed (#{e.message})", 'danger' + end + redirect_target = label['type'] == 'zpl' ? '/upload' : '/epson' + redirect redirect_target +end + +post '/labels/:id/delete' do + label = LABEL_REPO.find(params[:id]) + redirect_target = label && label['type'] == 'zpl' ? '/upload' : '/epson' + LABEL_REPO.delete(params[:id]) + flash 'Label deleted', 'success' + redirect redirect_target +end + get '/brother' do @printers = BROTHER_PRINTERS erb :brother_picker diff --git a/data/labels/.gitkeep b/data/labels/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/label_repository.rb b/lib/label_repository.rb new file mode 100644 index 0000000..6ca6875 --- /dev/null +++ b/lib/label_repository.rb @@ -0,0 +1,46 @@ +require 'json' +require 'securerandom' +require 'fileutils' + +# Simple file-backed store for reusable ZPL/ESC-POS label templates -- one +# JSON file per label under DIR, similar in spirit to brother_ql_web's own +# label repository. No per-printer targeting yet (type just says which print +# path to use); that can be added once labels can target any of the five +# printers via the Brother API. +class LabelRepository + def initialize(dir) + @dir = dir + FileUtils.mkdir_p(@dir) + end + + # type: "zpl" or "escpos" + def all(type: nil) + Dir.glob(File.join(@dir, '*.json')).map { |f| JSON.parse(File.read(f)) } + .select { |l| type.nil? || l['type'] == type } + .sort_by { |l| l['name'].downcase } + end + + def find(id) + path = path_for(id) + return nil unless File.exist?(path) + + JSON.parse(File.read(path)) + end + + def save(name:, type:, content:) + id = SecureRandom.uuid + label = { 'id' => id, 'name' => name, 'type' => type, 'content' => content } + File.write(path_for(id), JSON.generate(label)) + label + end + + def delete(id) + File.delete(path_for(id)) if File.exist?(path_for(id)) + end + + private + + def path_for(id) + File.join(@dir, "#{id}.json") + end +end diff --git a/views/_navbar.erb b/views/_navbar.erb index 6f129ce..d816717 100644 --- a/views/_navbar.erb +++ b/views/_navbar.erb @@ -7,21 +7,26 @@
diff --git a/views/epson.erb b/views/epson.erb index 197f5a5..655bc47 100644 --- a/views/epson.erb +++ b/views/epson.erb @@ -1,3 +1,4 @@ +Sent as-is (just reset + code page prefix and a trailing cut), no title/body template applied.
+ + + <% if @labels && !@labels.empty? %> +CAN
- Mainsail +Connected printers:
++ Labelary ZPL viewer + (preview/build ZPL before pasting it here) +
+ + + <% if @labels && !@labels.empty? %> +