update: driver upst
parent
ddd831e390
commit
a6584554f2
|
|
@ -0,0 +1,262 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace eSPJ.Controllers.SpjDriverUpstController
|
||||||
|
{
|
||||||
|
[Route("upst/detail-penjemputan")]
|
||||||
|
public class DetailPenjemputanController : Controller
|
||||||
|
{
|
||||||
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
|
||||||
|
public DetailPenjemputanController(IHttpClientFactory httpClientFactory, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
_httpClientFactory = httpClientFactory;
|
||||||
|
_configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("")]
|
||||||
|
public IActionResult Index()
|
||||||
|
{
|
||||||
|
return View("~/Views/Admin/Transport/SpjDriverUpst/DetailPenjemputan/Index.cshtml");
|
||||||
|
}
|
||||||
|
[HttpGet("tanpa-tps")]
|
||||||
|
public IActionResult TanpaTps()
|
||||||
|
{
|
||||||
|
return View("~/Views/Admin/Transport/SpjDriverUpst/DetailPenjemputan/TanpaTps.cshtml");
|
||||||
|
}
|
||||||
|
[HttpPost("")]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public IActionResult Index(
|
||||||
|
string? Latitude,
|
||||||
|
string? Longitude,
|
||||||
|
string? AlamatJalan,
|
||||||
|
string? GpsTruck,
|
||||||
|
string? WaktuKedatangan,
|
||||||
|
decimal? TotalTimbangan,
|
||||||
|
List<decimal>? BeratTimbangan,
|
||||||
|
List<IFormFile>? FotoKedatangan,
|
||||||
|
List<IFormFile>? FotoTimbangan,
|
||||||
|
List<IFormFile>? FotoPetugas)
|
||||||
|
{
|
||||||
|
if (FotoKedatangan == null || FotoKedatangan.Count == 0)
|
||||||
|
{
|
||||||
|
TempData["Error"] = "Step 1 wajib upload minimal 1 foto kedatangan.";
|
||||||
|
return RedirectToAction(nameof(Index));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FotoTimbangan == null || FotoTimbangan.Count == 0)
|
||||||
|
{
|
||||||
|
TempData["Error"] = "Step 2 wajib upload minimal 1 foto timbangan.";
|
||||||
|
return RedirectToAction(nameof(Index));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FotoPetugas == null || FotoPetugas.Count == 0)
|
||||||
|
{
|
||||||
|
TempData["Error"] = "Step 3 wajib upload minimal 1 foto petugas.";
|
||||||
|
return RedirectToAction(nameof(Index));
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalByDetail = (BeratTimbangan ?? new List<decimal>())
|
||||||
|
.Where(x => x > 0)
|
||||||
|
.Sum();
|
||||||
|
|
||||||
|
var total = TotalTimbangan.GetValueOrDefault() > 0
|
||||||
|
? TotalTimbangan.GetValueOrDefault()
|
||||||
|
: totalByDetail;
|
||||||
|
|
||||||
|
var totalDisplay = total.ToString("N2", CultureInfo.GetCultureInfo("id-ID"));
|
||||||
|
|
||||||
|
TempData["Success"] =
|
||||||
|
$"Data tersimpan. Kedatangan: {FotoKedatangan.Count} foto, " +
|
||||||
|
$"Timbangan: {FotoTimbangan.Count} foto, Total: {totalDisplay} kg, " +
|
||||||
|
$"Petugas: {FotoPetugas.Count} foto.";
|
||||||
|
|
||||||
|
// TODO: simpan ke database
|
||||||
|
_ = Latitude;
|
||||||
|
_ = Longitude;
|
||||||
|
_ = AlamatJalan;
|
||||||
|
_ = GpsTruck;
|
||||||
|
_ = WaktuKedatangan;
|
||||||
|
|
||||||
|
return RedirectToAction(nameof(Index));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("ocr-timbangan")]
|
||||||
|
[IgnoreAntiforgeryToken]
|
||||||
|
public async Task<IActionResult> OcrTimbangan(IFormFile? Foto)
|
||||||
|
{
|
||||||
|
if (Foto == null || Foto.Length == 0)
|
||||||
|
{
|
||||||
|
return BadRequest(new { success = false, message = "Foto tidak ditemukan." });
|
||||||
|
}
|
||||||
|
|
||||||
|
// limit size biar ga gila (contoh 5MB)
|
||||||
|
if (Foto.Length > 5 * 1024 * 1024)
|
||||||
|
{
|
||||||
|
return BadRequest(new { success = false, message = "Ukuran foto terlalu besar. Maksimal 5MB." });
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiKey = _configuration["OpenRouter:OCRkey"];
|
||||||
|
if (string.IsNullOrWhiteSpace(apiKey))
|
||||||
|
{
|
||||||
|
return StatusCode(500, new { success = false, message = "OpenRouter API key belum diset." });
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] fileBytes;
|
||||||
|
await using (var ms = new MemoryStream())
|
||||||
|
{
|
||||||
|
await Foto.CopyToAsync(ms);
|
||||||
|
fileBytes = ms.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
var mimeType = string.IsNullOrWhiteSpace(Foto.ContentType) ? "image/jpeg" : Foto.ContentType;
|
||||||
|
var base64 = Convert.ToBase64String(fileBytes);
|
||||||
|
var dataUrl = $"data:{mimeType};base64,{base64}";
|
||||||
|
|
||||||
|
var payload = new
|
||||||
|
{
|
||||||
|
model = "nvidia/nemotron-nano-12b-v2-vl:free",
|
||||||
|
temperature = 0,
|
||||||
|
messages = new object[]
|
||||||
|
{
|
||||||
|
new
|
||||||
|
{
|
||||||
|
role = "user",
|
||||||
|
content = new object[]
|
||||||
|
{
|
||||||
|
new
|
||||||
|
{
|
||||||
|
type = "text",
|
||||||
|
text = @"
|
||||||
|
Baca angka berat timbangan digital pada foto.
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
- Abaikan tulisan seperti ZERO, TARE, STABLE, AC, PACK, PCS, KG, ADD, HOLD.
|
||||||
|
- Jawab hanya angka dengan format 2 digit desimal pakai titik (contoh: 54.45).
|
||||||
|
- Jika tidak terbaca jawab: UNREADABLE
|
||||||
|
- Fokus pada angka layar LED merah yang menyala.
|
||||||
|
|
||||||
|
Saya berikan 3 contoh foto timbangan yang benar:
|
||||||
|
Foto 1 = 75.23
|
||||||
|
Foto 2 = 79.86
|
||||||
|
Foto 3 = 54.45
|
||||||
|
|
||||||
|
Sekarang baca angka pada foto terakhir."
|
||||||
|
},
|
||||||
|
|
||||||
|
new
|
||||||
|
{
|
||||||
|
type = "image_url",
|
||||||
|
image_url = new { url = "https://res.cloudinary.com/drejcprhe/image/upload/v1770888384/Notes_-_2026-02-11_08.52.31_wonhbm.jpg" }
|
||||||
|
},
|
||||||
|
|
||||||
|
new
|
||||||
|
{
|
||||||
|
type = "image_url",
|
||||||
|
image_url = new { url = "https://res.cloudinary.com/drejcprhe/image/upload/v1770888429/Notes_-_2026-02-11_08.52.34_xairzy.jpg" }
|
||||||
|
},
|
||||||
|
|
||||||
|
new
|
||||||
|
{
|
||||||
|
type = "image_url",
|
||||||
|
image_url = new { url = "https://res.cloudinary.com/drejcprhe/image/upload/v1770888473/ChatGPT_Image_Feb_11_2026_03_00_33_PM_ujhdlw.png" }
|
||||||
|
},
|
||||||
|
|
||||||
|
new
|
||||||
|
{
|
||||||
|
type = "image_url",
|
||||||
|
image_url = new { url = dataUrl }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var json = JsonSerializer.Serialize(payload);
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post, "https://openrouter.ai/api/v1/chat/completions");
|
||||||
|
request.Headers.TryAddWithoutValidation("Authorization", $"Bearer {apiKey}");
|
||||||
|
request.Headers.TryAddWithoutValidation("Accept", "application/json");
|
||||||
|
request.Headers.TryAddWithoutValidation("HTTP-Referer", "https://yourdomain.com");
|
||||||
|
request.Headers.TryAddWithoutValidation("X-Title", "eSPJ OCR Timbangan");
|
||||||
|
|
||||||
|
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
var client = _httpClientFactory.CreateClient();
|
||||||
|
using var response = await client.SendAsync(request);
|
||||||
|
var responseText = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return StatusCode((int)response.StatusCode, new
|
||||||
|
{
|
||||||
|
success = false,
|
||||||
|
message = "OpenRouter request gagal.",
|
||||||
|
detail = responseText
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
using var doc = JsonDocument.Parse(responseText);
|
||||||
|
|
||||||
|
var content = doc.RootElement
|
||||||
|
.GetProperty("choices")[0]
|
||||||
|
.GetProperty("message")
|
||||||
|
.GetProperty("content")
|
||||||
|
.GetString() ?? "";
|
||||||
|
|
||||||
|
content = content.Trim();
|
||||||
|
|
||||||
|
if (content.Contains("UNREADABLE", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
success = false,
|
||||||
|
message = "Angka tidak terbaca.",
|
||||||
|
raw = content
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// cari format angka 2 desimal
|
||||||
|
var match = Regex.Match(content, @"-?\d{1,5}([.,]\d{2})");
|
||||||
|
|
||||||
|
if (!match.Success)
|
||||||
|
{
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
success = false,
|
||||||
|
message = "AI tidak menemukan angka valid.",
|
||||||
|
raw = content
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalized = match.Value.Replace(',', '.');
|
||||||
|
|
||||||
|
if (!decimal.TryParse(normalized, NumberStyles.Any, CultureInfo.InvariantCulture, out var weight))
|
||||||
|
{
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
success = false,
|
||||||
|
message = "Format angka AI tidak valid.",
|
||||||
|
raw = content
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
success = true,
|
||||||
|
weight = weight.ToString("0.00", CultureInfo.InvariantCulture),
|
||||||
|
raw = content
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("batal")]
|
||||||
|
public IActionResult Batal()
|
||||||
|
{
|
||||||
|
return View("~/Views/Admin/Transport/SpjDriverUpst/DetailPenjemputan/Batal.cshtml");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace eSPJ.Controllers.SpjDriverUpstController
|
||||||
|
{
|
||||||
|
[Route("upst/history")]
|
||||||
|
public class HistoryController : Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
[HttpGet("")]
|
||||||
|
public IActionResult Index()
|
||||||
|
{
|
||||||
|
return View("~/Views/Admin/Transport/SpjDriverUpst/History/Index.cshtml");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("details/{id}")]
|
||||||
|
public IActionResult Details(int id)
|
||||||
|
{
|
||||||
|
ViewData["Id"] = id;
|
||||||
|
return View("~/Views/Admin/Transport/SpjDriverUpst/History/Details.cshtml");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using eSPJ.Models;
|
||||||
|
|
||||||
|
namespace eSPJ.Controllers.SpjDriverUpstController;
|
||||||
|
|
||||||
|
[Route("upst")]
|
||||||
|
public class HomeController : Controller
|
||||||
|
{
|
||||||
|
private readonly ILogger<HomeController> _logger;
|
||||||
|
|
||||||
|
public HomeController(ILogger<HomeController> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
[HttpGet("")]
|
||||||
|
public IActionResult Index()
|
||||||
|
{
|
||||||
|
return View("~/Views/Admin/Transport/SpjDriverUpst/Home/Index.cshtml");
|
||||||
|
}
|
||||||
|
[HttpGet("kosong")]
|
||||||
|
public IActionResult Kosong()
|
||||||
|
{
|
||||||
|
return View("~/Views/Admin/Transport/SpjDriverUpst/Home/Kosong.cshtml");
|
||||||
|
}
|
||||||
|
|
||||||
|
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||||
|
public IActionResult Error()
|
||||||
|
{
|
||||||
|
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
|
||||||
|
namespace eSPJ.Controllers.SpjDriverUpstController
|
||||||
|
{
|
||||||
|
[Route("upst/login")]
|
||||||
|
public class LoginController : Controller
|
||||||
|
{
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
|
||||||
|
public LoginController(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
_configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("")]
|
||||||
|
public IActionResult Index()
|
||||||
|
{
|
||||||
|
ViewBag.SSOLoginUrl = _configuration["SSO:LoginUrl"];
|
||||||
|
return View("~/Views/Admin/Transport/SpjDriverUpst/Login/Index.cshtml");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace eSPJ.Controllers.SpjDriverUpstController
|
||||||
|
{
|
||||||
|
[Route("upst/profil")]
|
||||||
|
public class ProfilController : Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
[HttpGet("")]
|
||||||
|
public IActionResult Index()
|
||||||
|
{
|
||||||
|
return View("~/Views/Admin/Transport/SpjDriverUpst/Profil/Index.cshtml");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace eSPJ.Controllers.SpjDriverUpstController
|
||||||
|
{
|
||||||
|
[Route("upst/submit")]
|
||||||
|
public class SubmitController : Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
[HttpGet("")]
|
||||||
|
public IActionResult Index()
|
||||||
|
{
|
||||||
|
return View("~/Views/Admin/Transport/SpjDriverUpst/Submit/Index.cshtml");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpGet("struk")]
|
||||||
|
public IActionResult Struk()
|
||||||
|
{
|
||||||
|
return View("~/Views/Admin/Transport/SpjDriverUpst/Submit/Struk.cshtml");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("struk")]
|
||||||
|
public IActionResult ProcessStruk(string NomorStruk, string NomorPolisi, string Penugasan,
|
||||||
|
string WaktuMasuk, string WaktuKeluar, int? BeratMasuk, int? BeratKeluar, int BeratNett)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Validate required inputs
|
||||||
|
if (string.IsNullOrEmpty(NomorStruk) || BeratNett <= 0)
|
||||||
|
{
|
||||||
|
TempData["Error"] = "Nomor struk dan berat nett harus diisi.";
|
||||||
|
return RedirectToAction("Struk");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate receipt number format (numbers only, 7+ digits)
|
||||||
|
if (!System.Text.RegularExpressions.Regex.IsMatch(NomorStruk, @"^\d{7,}$"))
|
||||||
|
{
|
||||||
|
TempData["Error"] = "Format nomor struk tidak valid. Harus berupa angka minimal 7 digit.";
|
||||||
|
return RedirectToAction("Struk");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate weight range
|
||||||
|
if (BeratNett < 100 || BeratNett > 50000)
|
||||||
|
{
|
||||||
|
TempData["Error"] = "Berat nett harus antara 100 kg - 50,000 kg.";
|
||||||
|
return RedirectToAction("Struk");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate optional weights
|
||||||
|
if (BeratMasuk.HasValue && (BeratMasuk < 0 || BeratMasuk > 100000))
|
||||||
|
{
|
||||||
|
TempData["Error"] = "Berat masuk tidak valid.";
|
||||||
|
return RedirectToAction("Struk");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BeratKeluar.HasValue && (BeratKeluar < 0 || BeratKeluar > 100000))
|
||||||
|
{
|
||||||
|
TempData["Error"] = "Berat keluar tidak valid.";
|
||||||
|
return RedirectToAction("Struk");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here you would normally save to database
|
||||||
|
// For now, just simulate success with all data
|
||||||
|
var submitData = new
|
||||||
|
{
|
||||||
|
NomorStruk,
|
||||||
|
NomorPolisi = NomorPolisi ?? "N/A",
|
||||||
|
Penugasan = Penugasan ?? "N/A",
|
||||||
|
WaktuMasuk = WaktuMasuk ?? "N/A",
|
||||||
|
WaktuKeluar = WaktuKeluar ?? "N/A",
|
||||||
|
BeratMasuk = BeratMasuk?.ToString() ?? "N/A",
|
||||||
|
BeratKeluar = BeratKeluar?.ToString() ?? "N/A",
|
||||||
|
BeratNett
|
||||||
|
};
|
||||||
|
|
||||||
|
TempData["Success"] = $"Struk berhasil disubmit! No: {NomorStruk}, Nett: {BeratNett} kg";
|
||||||
|
return RedirectToAction("Index", "Home");
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
TempData["Error"] = "Terjadi kesalahan saat memproses struk. Silakan coba lagi.";
|
||||||
|
return RedirectToAction("Struk");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
builder.Services.AddControllersWithViews();
|
builder.Services.AddControllersWithViews();
|
||||||
|
builder.Services.AddHttpClient();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,179 +0,0 @@
|
||||||
# SPJ Barcode Scanner
|
|
||||||
|
|
||||||
Scanner barcode untuk aplikasi eSPJ yang menggunakan QuaggaJS library.
|
|
||||||
|
|
||||||
## Fitur
|
|
||||||
|
|
||||||
- Scanner barcode real-time menggunakan kamera device
|
|
||||||
- Mendukung berbagai format barcode (Code 128, Code 39, EAN, dll)
|
|
||||||
- Input manual sebagai alternatif
|
|
||||||
- Validasi format SPJ
|
|
||||||
- Responsive design untuk mobile dan desktop
|
|
||||||
- Sound feedback saat barcode terdeteksi
|
|
||||||
|
|
||||||
## Format Barcode yang Didukung
|
|
||||||
|
|
||||||
- Code 128
|
|
||||||
- Code 39
|
|
||||||
- Code 39 VIN
|
|
||||||
- EAN-13
|
|
||||||
- EAN-8
|
|
||||||
- Code 93
|
|
||||||
|
|
||||||
## Cara Penggunaan
|
|
||||||
|
|
||||||
### Untuk User (Driver)
|
|
||||||
|
|
||||||
1. **Akses Halaman Scanner**
|
|
||||||
|
|
||||||
- Buka aplikasi eSPJ
|
|
||||||
- Navigasi ke halaman "Scan SPJ"
|
|
||||||
|
|
||||||
2. **Menggunakan Camera Scanner**
|
|
||||||
|
|
||||||
- Klik tombol "Mulai Scan"
|
|
||||||
- Izinkan akses kamera saat diminta browser
|
|
||||||
- Arahkan kamera ke barcode SPJ
|
|
||||||
- Scanner akan otomatis mendeteksi dan menampilkan hasil
|
|
||||||
- Klik "Konfirmasi" untuk melanjutkan atau "Scan Ulang" untuk mencoba lagi
|
|
||||||
|
|
||||||
3. **Menggunakan Input Manual**
|
|
||||||
- Masukkan kode SPJ secara manual di field yang disediakan
|
|
||||||
- Klik tombol search untuk memproses
|
|
||||||
|
|
||||||
### Browser Requirements
|
|
||||||
|
|
||||||
- Chrome 21+
|
|
||||||
- Firefox 17+
|
|
||||||
- Safari 11+
|
|
||||||
- Edge 12+
|
|
||||||
- Opera 18+
|
|
||||||
|
|
||||||
### Permissions
|
|
||||||
|
|
||||||
Scanner memerlukan akses kamera. Pastikan:
|
|
||||||
|
|
||||||
- Akses kamera diizinkan pada browser
|
|
||||||
- Halaman diakses melalui HTTPS (untuk production)
|
|
||||||
- Device memiliki kamera yang berfungsi
|
|
||||||
|
|
||||||
## Technical Implementation
|
|
||||||
|
|
||||||
### Dependencies
|
|
||||||
|
|
||||||
- **QuaggaJS**: Library untuk barcode scanning
|
|
||||||
- **Lucide Icons**: Untuk iconography
|
|
||||||
- **Tailwind CSS**: Untuk styling
|
|
||||||
|
|
||||||
### File Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
Views/Admin/Transport/SpjDriver/Scan/
|
|
||||||
├── Index.cshtml # Main scanner page
|
|
||||||
Controllers/SpjDriverController/
|
|
||||||
├── ScanController.cs # Backend logic
|
|
||||||
wwwroot/driver/css/
|
|
||||||
├── scanner.css # Scanner-specific styles
|
|
||||||
```
|
|
||||||
|
|
||||||
### Key Components
|
|
||||||
|
|
||||||
1. **BarcodeScanner Class** (JavaScript)
|
|
||||||
|
|
||||||
- Handles camera initialization
|
|
||||||
- Manages QuaggaJS configuration
|
|
||||||
- Processes scan results
|
|
||||||
- Handles UI interactions
|
|
||||||
|
|
||||||
2. **ScanController** (C#)
|
|
||||||
- Validates scanned codes
|
|
||||||
- Processes SPJ lookup
|
|
||||||
- Handles error responses
|
|
||||||
|
|
||||||
### Configuration
|
|
||||||
|
|
||||||
QuaggaJS configuration:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
{
|
|
||||||
inputStream: {
|
|
||||||
type: "LiveStream",
|
|
||||||
constraints: {
|
|
||||||
width: 320,
|
|
||||||
height: 240,
|
|
||||||
facingMode: "environment" // Use back camera
|
|
||||||
}
|
|
||||||
},
|
|
||||||
decoder: {
|
|
||||||
readers: [
|
|
||||||
"code_128_reader",
|
|
||||||
"code_39_reader",
|
|
||||||
"ean_reader",
|
|
||||||
// ... more readers
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Customization
|
|
||||||
|
|
||||||
### Menambah Format Barcode Baru
|
|
||||||
|
|
||||||
Edit array `readers` di file Index.cshtml:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
readers: ["code_128_reader", "your_new_reader_here"];
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mengubah Validasi SPJ
|
|
||||||
|
|
||||||
Edit method `ValidateSpjCode` di ScanController.cs:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
private async Task<SpjData?> ValidateSpjCode(string barcode)
|
|
||||||
{
|
|
||||||
// Your custom validation logic here
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Styling
|
|
||||||
|
|
||||||
Edit file `scanner.css` untuk mengubah appearance scanner.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Camera Tidak Berfungsi
|
|
||||||
|
|
||||||
1. Pastikan browser memiliki akses kamera
|
|
||||||
2. Cek apakah halaman diakses melalui HTTPS
|
|
||||||
3. Restart browser jika perlu
|
|
||||||
4. Cek device permissions
|
|
||||||
|
|
||||||
### Barcode Tidak Terdeteksi
|
|
||||||
|
|
||||||
1. Pastikan barcode dalam format yang didukung
|
|
||||||
2. Cek pencahayaan - barcode harus jelas terbaca
|
|
||||||
3. Jaga jarak optimal (15-30cm dari kamera)
|
|
||||||
4. Pastikan barcode tidak rusak atau blur
|
|
||||||
|
|
||||||
### Performance Issues
|
|
||||||
|
|
||||||
1. Tutup aplikasi lain yang menggunakan kamera
|
|
||||||
2. Gunakan browser yang up-to-date
|
|
||||||
3. Cek kecepatan internet untuk loading library
|
|
||||||
|
|
||||||
## Development Notes
|
|
||||||
|
|
||||||
- Library QuaggaJS dimuat dari CDN (dapat diunduh lokal jika perlu)
|
|
||||||
- Scanner otomatis stop setelah berhasil scan untuk menghemat resource
|
|
||||||
- Implementasi includes sound feedback dan haptic feedback
|
|
||||||
- Mobile-first responsive design
|
|
||||||
|
|
||||||
## Future Enhancements
|
|
||||||
|
|
||||||
- [ ] Support untuk QR Code
|
|
||||||
- [ ] Batch scanning multiple barcodes
|
|
||||||
- [ ] Offline scanning capability
|
|
||||||
- [ ] Advanced barcode validation
|
|
||||||
- [ ] Scan history
|
|
||||||
- [ ] Analytics dan reporting
|
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Admin/Transport/SpjDriverUpst/Shared/_Layout.cshtml";
|
||||||
|
ViewData["Title"] = "Detail Batal Penjemputan";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="w-full lg:max-w-sm mx-auto bg-gray-50 min-h-screen">
|
||||||
|
<div class="bg-gradient-to-r from-orange-500 to-orange-600 text-white px-4 py-4 sticky top-0 z-10 shadow-lg">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<a href="@Url.Action("Index", "Home")" class="p-2 hover:bg-white/10 rounded-full transition-all duration-200">
|
||||||
|
<i class="w-5 h-5" data-lucide="chevron-left"></i>
|
||||||
|
</a>
|
||||||
|
<h1 class="text-lg font-bold">Pembatalan Penjemputan</h1>
|
||||||
|
<div class="w-9"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="px-4 pt-4">
|
||||||
|
<div class="bg-white rounded-2xl p-4 shadow-sm border border-gray-100 mb-4">
|
||||||
|
<div class="flex items-center gap-3 mb-3">
|
||||||
|
<div class="w-10 h-10 bg-orange-100 rounded-full flex items-center justify-center">
|
||||||
|
<i class="w-5 h-5 text-orange-600" data-lucide="map-pin"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="font-bold text-gray-900">CV Tri Berkah Sejahtera</h3>
|
||||||
|
<p class="text-xs text-gray-500">Lokasi yang dibatalkan</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-gray-700 leading-relaxed">
|
||||||
|
Kp. Pertanian II Rt.004 Rw.001 Kel. Klender Kec, Duren Sawit, Kota Adm. Jakarta Timur 13470
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="px-4 pb-6">
|
||||||
|
<div class="bg-white rounded-2xl p-5 shadow-sm border border-gray-100">
|
||||||
|
<div class="flex items-center gap-3 mb-4">
|
||||||
|
<div class="w-10 h-10 bg-orange-100 rounded-full flex items-center justify-center">
|
||||||
|
<i class="w-5 h-5 text-orange-600" data-lucide="file-text"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 class="text-lg font-bold text-gray-900">Form Pembatalan</h2>
|
||||||
|
<p class="text-xs text-gray-500">Berikan keterangan</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form asp-action="Batal" method="post" class="space-y-5">
|
||||||
|
<!-- Alasan Pembatalan -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-semibold text-gray-700 mb-3">Alasan Pembatalan</label>
|
||||||
|
<textarea name="AlasanPembatalan"
|
||||||
|
class="w-full rounded-xl text-sm border border-gray-300 focus:border-red-500 focus:ring-2 focus:ring-red-200 p-4 text-gray-700 resize-none transition-all duration-200"
|
||||||
|
rows="5"
|
||||||
|
placeholder="Jelaskan alasan pembatalan penjemputan..."
|
||||||
|
required></textarea>
|
||||||
|
<div class="text-red-500 text-sm mt-2" id="validation-message" style="display: none;">
|
||||||
|
Harap isi alasan pembatalan
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-3 pt-4">
|
||||||
|
<a href="@Url.Action("Index", "Home")"
|
||||||
|
class="flex-1 bg-gray-200 text-gray-700 font-semibold py-3 rounded-xl text-center hover:bg-gray-300 transition-colors">
|
||||||
|
Batal
|
||||||
|
</a>
|
||||||
|
<button type="submit"
|
||||||
|
class="flex-1 bg-gradient-to-r from-orange-500 to-orange-600 text-white font-semibold py-3 rounded-xl hover:from-orange-600 hover:to-orange-700 transition-all duration-200 shadow-lg">
|
||||||
|
Konfirmasi
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Bottom Navigation -->
|
||||||
|
<partial name="~/Views/Admin/Transport/SpjDriverUpst/Shared/Components/_Navigation.cshtml" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<register-block dynamic-section="scripts" key="jsDetailBatal">
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const alasanTextarea = document.querySelector('textarea[name="AlasanPembatalan"]');
|
||||||
|
const form = document.querySelector('form');
|
||||||
|
const validationMessage = document.getElementById('validation-message');
|
||||||
|
|
||||||
|
form.addEventListener('submit', function(e) {
|
||||||
|
if (!alasanTextarea.value.trim()) {
|
||||||
|
e.preventDefault();
|
||||||
|
validationMessage.style.display = 'block';
|
||||||
|
alasanTextarea.focus();
|
||||||
|
alasanTextarea.style.borderColor = '#ef4444';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
validationMessage.style.display = 'none';
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
alasanTextarea.addEventListener('input', function() {
|
||||||
|
if (this.value.trim()) {
|
||||||
|
validationMessage.style.display = 'none';
|
||||||
|
this.style.borderColor = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</register-block>
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,879 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Admin/Transport/SpjDriverUpst/Shared/_Layout.cshtml";
|
||||||
|
ViewData["Title"] = "Detail Penjemputan - Tanpa TPS";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="w-full lg:max-w-sm mx-auto min-h-screen bg-gray-50 pb-24">
|
||||||
|
<div class="bg-upst text-white px-6 pt-8 pb-16 rounded-b-[40px] shadow-lg relative">
|
||||||
|
<div class="flex items-center justify-between relative z-10">
|
||||||
|
<a href="@Url.Action("Index", "Home")" class="w-10 h-10 flex items-center justify-center bg-white/10 rounded-xl backdrop-blur-md">
|
||||||
|
<i class="w-5 h-5" data-lucide="chevron-left"></i>
|
||||||
|
</a>
|
||||||
|
<h1 class="text-lg font-black uppercase tracking-tight">Detail Lokasi</h1>
|
||||||
|
<div class="w-10"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-center -mt-8 mb-6 px-6 relative z-20">
|
||||||
|
<div class="bg-white border border-gray-100 px-6 py-3 rounded-2xl flex items-center gap-4 w-full justify-between">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="text-[10px] font-black text-gray-400 uppercase tracking-widest">Plat Nomor</span>
|
||||||
|
<span id="plat-nomor" class="text-green-700 font-black text-xl leading-none">B 9632 TOR</span>
|
||||||
|
</div>
|
||||||
|
<div class="h-8 w-[1px] bg-gray-100"></div>
|
||||||
|
<div class="text-right">
|
||||||
|
<span class="text-[10px] font-black text-gray-400 uppercase tracking-widest">No. Pintu</span>
|
||||||
|
<p class="text-orange-500 font-bold text-sm">JRC 005</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="px-6 space-y-6">
|
||||||
|
<div class="bg-white rounded-[32px] p-6 border border-gray-100 relative overflow-hidden">
|
||||||
|
<div class="absolute top-0 right-0 p-4">
|
||||||
|
<span class="px-3 py-1 bg-gray-500 text-white rounded-full text-[10px] font-black tracking-tighter uppercase">
|
||||||
|
TANPA TPS
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<p class="text-[10px] text-gray-400 uppercase tracking-widest mb-1">Perusahaan</p>
|
||||||
|
<h2 class="text-xl font-black text-gray-800 leading-tight">CV Tri Berkah Sejahtera</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-center gap-2 bg-gray-50 p-2 rounded-xl">
|
||||||
|
<p class="text-gray-600 font-mono text-xl font-bold uppercase tracking-tighter">SPJ/07-2025/PKM/000476</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pt-2 border-t border-gray-50">
|
||||||
|
<div class="flex gap-2 items-start">
|
||||||
|
<p class="text-gray-500 text-xs font-medium leading-relaxed">
|
||||||
|
Kp. Pertanian II Rt.004 Rw.001 Kel. Klender Kec, Duren Sawit, Kota Adm. Jakarta Timur 13470
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pt-4 border-t border-gray-100 mt-4">
|
||||||
|
<div class="flex items-center justify-between mb-2">
|
||||||
|
<p class="text-[10px] text-gray-400 uppercase tracking-widest">Total Timbangan</p>
|
||||||
|
<span class="text-2xl font-black text-upst"><span id="grand-total-timbangan">0,00</span> kg</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="tps-tabs-container" class="bg-white rounded-[32px] p-6 border border-gray-100">
|
||||||
|
<div class="flex items-center gap-2 mb-4">
|
||||||
|
<div class="w-2 h-2 rounded-full bg-gray-400"></div>
|
||||||
|
<h3 class="font-black text-gray-800">Form Pengangkutan</h3>
|
||||||
|
<span class="ml-auto text-[10px] font-bold text-gray-500 bg-gray-100 px-3 py-1 rounded-full">Tanpa TPS</span>
|
||||||
|
</div>
|
||||||
|
<div id="tps-content">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (TempData["Success"] != null)
|
||||||
|
{
|
||||||
|
<div class="bg-green-50 border border-green-200 text-green-700 rounded-2xl p-3 text-sm font-medium">
|
||||||
|
@TempData["Success"]
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (TempData["Error"] != null)
|
||||||
|
{
|
||||||
|
<div class="bg-red-50 border border-red-200 text-red-700 rounded-2xl p-3 text-sm font-medium">
|
||||||
|
@TempData["Error"]
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<partial name="~/Views/Admin/Transport/SpjDriverUpst/Shared/Components/_Navigation.cshtml" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<register-block dynamic-section="scripts" key="jsDetailPenjemputanTanpaTps">
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const grandTotalDisplay = document.getElementById('grand-total-timbangan');
|
||||||
|
const tpsContentContainer = document.getElementById('tps-content');
|
||||||
|
|
||||||
|
let activeTpsIndex = 0;
|
||||||
|
let tpsData = [];
|
||||||
|
|
||||||
|
const OCR_AREAS = [
|
||||||
|
{ id: 'A', x: 0.34, y: 0.35, w: 0.40, h: 0.11, color: 'border-lime-400 bg-lime-500/15' },
|
||||||
|
{ id: 'B', x: 0.31, y: 0.33, w: 0.45, h: 0.14, color: 'border-amber-300 bg-amber-400/10' },
|
||||||
|
{ id: 'C', x: 0.29, y: 0.31, w: 0.49, h: 0.17, color: 'border-cyan-300 bg-cyan-400/10' }
|
||||||
|
];
|
||||||
|
|
||||||
|
function initializeLocation() {
|
||||||
|
tpsData = [{
|
||||||
|
name: '',
|
||||||
|
index: 0,
|
||||||
|
latitude: '',
|
||||||
|
longitude: '',
|
||||||
|
alamatJalan: '',
|
||||||
|
waktuKedatangan: '',
|
||||||
|
fotoKedatangan: [],
|
||||||
|
fotoKedatanganUploaded: false,
|
||||||
|
timbangan: [],
|
||||||
|
totalTimbangan: 0,
|
||||||
|
fotoPetugas: [],
|
||||||
|
fotoPetugasUploaded: false,
|
||||||
|
namaPetugas: '',
|
||||||
|
submitted: false
|
||||||
|
}];
|
||||||
|
|
||||||
|
renderTpsForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderTpsForm() {
|
||||||
|
const tps = tpsData[activeTpsIndex];
|
||||||
|
|
||||||
|
tpsContentContainer.innerHTML = `
|
||||||
|
<form class="space-y-5 pb-8" data-tps-index="${tps.index}">
|
||||||
|
<input type="hidden" class="tps-latitude" value="${tps.latitude}" />
|
||||||
|
<input type="hidden" class="tps-longitude" value="${tps.longitude}" />
|
||||||
|
<input type="hidden" class="tps-alamat-jalan" value="${tps.alamatJalan}" />
|
||||||
|
<input type="hidden" class="tps-total-timbangan" value="${tps.totalTimbangan}" />
|
||||||
|
|
||||||
|
<section class="bg-white border border-gray-100 rounded-3xl p-5 space-y-4">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="w-8 h-8 rounded-full bg-upst text-white font-black text-sm flex items-center justify-center">1</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="font-black text-gray-800">Foto Kedatangan</h3>
|
||||||
|
<p class="text-xs text-gray-500">Upload foto kedatangan</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class="block text-xs font-semibold text-gray-600">Upload Foto Kedatangan</label>
|
||||||
|
<input type="file" class="tps-foto-kedatangan block w-full text-sm text-gray-700 border border-gray-200 rounded-xl p-2 file:mr-3 file:rounded-lg file:border-0 file:bg-upst file:px-3 file:py-2 file:text-xs file:font-bold file:text-white" accept="image/*" multiple />
|
||||||
|
<div class="tps-preview-kedatangan space-y-2"></div>
|
||||||
|
|
||||||
|
${tps.fotoKedatangan.length > 0 && !tps.fotoKedatanganUploaded ? `
|
||||||
|
<button type="button" class="tps-btn-upload-kedatangan w-full bg-blue-500 text-white py-2 rounded-xl font-bold text-xs hover:brightness-110">
|
||||||
|
Upload ${tps.fotoKedatangan.length} Foto Kedatangan
|
||||||
|
</button>
|
||||||
|
` : tps.fotoKedatanganUploaded ? `
|
||||||
|
<div class="text-center text-xs text-green-600 font-bold py-2">
|
||||||
|
✓ Foto kedatangan sudah diupload
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-2">
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-semibold text-gray-600 mb-1">Latitude</label>
|
||||||
|
<input type="text" class="tps-display-latitude w-full rounded-xl border border-gray-200 bg-gray-50 px-3 py-2 text-xs" readonly value="${tps.latitude}" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-semibold text-gray-600 mb-1">Longitude</label>
|
||||||
|
<input type="text" class="tps-display-longitude w-full rounded-xl border border-gray-200 bg-gray-50 px-3 py-2 text-xs" readonly value="${tps.longitude}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-semibold text-gray-600 mb-1">Waktu Kedatangan</label>
|
||||||
|
<input type="text" class="tps-waktu-kedatangan w-full rounded-xl border border-gray-200 bg-gray-50 px-3 py-2 text-xs" readonly value="${tps.waktuKedatangan}" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="bg-white border border-gray-100 rounded-3xl p-5 space-y-4">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="w-8 h-8 rounded-full bg-upst text-white font-black text-sm flex items-center justify-center">2</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="font-black text-gray-800">Foto Timbang Sampah</h3>
|
||||||
|
<p class="text-xs text-gray-500">Upload foto timbangan, berat auto terisi</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tps-timbangan-repeater space-y-3"></div>
|
||||||
|
|
||||||
|
<button type="button" class="tps-btn-add-timbangan w-full border border-dashed border-upst text-upst rounded-xl py-2 text-xs font-bold transition">
|
||||||
|
+ Tambah Foto Timbangan
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="rounded-xl bg-gray-50 border border-gray-200 px-3 py-2 flex items-center justify-between">
|
||||||
|
<span class="text-xs font-semibold text-gray-600">Total Timbangan</span>
|
||||||
|
<span class="text-base font-black text-upst"><span class="tps-display-total">${formatWeightDisplay(tps.totalTimbangan)}</span> kg</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="bg-white border border-gray-100 rounded-3xl p-5 space-y-4">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="w-8 h-8 rounded-full bg-upst text-white font-black text-sm flex items-center justify-center">3</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="font-black text-gray-800">Foto Petugas</h3>
|
||||||
|
<p class="text-xs text-gray-500">Upload dokumentasi petugas</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class="block text-xs font-semibold text-gray-600">Upload Foto Petugas</label>
|
||||||
|
<input type="file" class="tps-foto-petugas block w-full text-sm text-gray-700 border border-gray-200 rounded-xl p-2 file:mr-3 file:rounded-lg file:border-0 file:bg-upst file:px-3 file:py-2 file:text-xs file:font-bold file:text-white" accept="image/*" multiple />
|
||||||
|
<div class="tps-preview-petugas space-y-2"></div>
|
||||||
|
|
||||||
|
${tps.fotoPetugas.length > 0 && !tps.fotoPetugasUploaded ? `
|
||||||
|
<button type="button" class="tps-btn-upload-petugas w-full bg-blue-500 text-white py-2 rounded-xl font-bold text-xs hover:brightness-110">
|
||||||
|
Upload ${tps.fotoPetugas.length} Foto Petugas
|
||||||
|
</button>
|
||||||
|
` : tps.fotoPetugasUploaded ? `
|
||||||
|
<div class="text-center text-xs text-green-600 font-bold py-2">
|
||||||
|
✓ Foto petugas sudah diupload
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-semibold text-gray-600 mb-1">Nama Petugas</label>
|
||||||
|
<input type="text" class="tps-nama-petugas w-full rounded-xl border border-gray-200 px-3 py-2 text-sm" placeholder="Masukkan nama petugas" value="${tps.namaPetugas}" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="flex gap-3">
|
||||||
|
<a href="@Url.Action("Batal", "DetailPenjemputan")" class="w-1/3 text-center bg-red-500 text-white py-3 rounded-xl font-bold text-sm">Batal</a>
|
||||||
|
<button type="submit" class="w-2/3 bg-upst text-white py-3 rounded-xl font-bold text-sm hover:brightness-110">Submit</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
`;
|
||||||
|
|
||||||
|
attachTpsFormListeners();
|
||||||
|
restoreTpsTimbanganItems();
|
||||||
|
restorePhotoPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
function restorePhotoPreview() {
|
||||||
|
const tps = tpsData[activeTpsIndex];
|
||||||
|
const form = tpsContentContainer.querySelector('form');
|
||||||
|
if (!form) return;
|
||||||
|
|
||||||
|
const previewKedatangan = form.querySelector('.tps-preview-kedatangan');
|
||||||
|
if (previewKedatangan && tps.fotoKedatangan.length > 0) {
|
||||||
|
renderStoredPhotos(tps.fotoKedatangan, previewKedatangan);
|
||||||
|
}
|
||||||
|
|
||||||
|
const previewPetugas = form.querySelector('.tps-preview-petugas');
|
||||||
|
if (previewPetugas && tps.fotoPetugas.length > 0) {
|
||||||
|
renderStoredPhotos(tps.fotoPetugas, previewPetugas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderStoredPhotos(files, container) {
|
||||||
|
container.innerHTML = '';
|
||||||
|
container.className = 'space-y-2';
|
||||||
|
|
||||||
|
files.forEach((file, index) => {
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.className = 'rounded-xl border border-gray-200 overflow-hidden bg-black';
|
||||||
|
|
||||||
|
const imageUrl = URL.createObjectURL(file);
|
||||||
|
const safeName = file.name.replace(/"/g, '"');
|
||||||
|
item.innerHTML = `
|
||||||
|
<div class="h-44 bg-black/80">
|
||||||
|
<img src="${imageUrl}" alt="Preview ${index + 1}" class="w-full h-full object-contain preview-multi-image" />
|
||||||
|
</div>
|
||||||
|
<div class="px-2 py-1 bg-white">
|
||||||
|
<p class="text-[11px] font-semibold text-gray-700 truncate">${index + 1}. ${safeName}</p>
|
||||||
|
<p class="text-[10px] text-gray-500">${formatFileSize(file.size)}</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const img = item.querySelector('.preview-multi-image');
|
||||||
|
if (img) {
|
||||||
|
img.onload = function() {
|
||||||
|
URL.revokeObjectURL(imageUrl);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
container.appendChild(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachTpsFormListeners() {
|
||||||
|
const form = tpsContentContainer.querySelector('form');
|
||||||
|
const tps = tpsData[activeTpsIndex];
|
||||||
|
|
||||||
|
const fotoKedatanganInput = form.querySelector('.tps-foto-kedatangan');
|
||||||
|
const fotoPetugasInput = form.querySelector('.tps-foto-petugas');
|
||||||
|
const namaPetugasInput = form.querySelector('.tps-nama-petugas');
|
||||||
|
const btnAddTimbangan = form.querySelector('.tps-btn-add-timbangan');
|
||||||
|
const toggleDebug = form.querySelector('.tps-toggle-debug-crop');
|
||||||
|
|
||||||
|
fotoKedatanganInput.addEventListener('change', function() {
|
||||||
|
tps.fotoKedatangan = Array.from(this.files);
|
||||||
|
tps.fotoKedatanganUploaded = false;
|
||||||
|
updateWaktuKedatangan();
|
||||||
|
updateMultiPreview(this, form.querySelector('.tps-preview-kedatangan'));
|
||||||
|
renderTpsForm(); // Re-render to show upload button
|
||||||
|
});
|
||||||
|
|
||||||
|
fotoPetugasInput.addEventListener('change', function() {
|
||||||
|
tps.fotoPetugas = Array.from(this.files);
|
||||||
|
tps.fotoPetugasUploaded = false;
|
||||||
|
updateMultiPreview(this, form.querySelector('.tps-preview-petugas'));
|
||||||
|
renderTpsForm();
|
||||||
|
});
|
||||||
|
|
||||||
|
namaPetugasInput.addEventListener('input', function() {
|
||||||
|
tps.namaPetugas = this.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
btnAddTimbangan.addEventListener('click', function() {
|
||||||
|
createTimbanganItem(form.querySelector('.tps-timbangan-repeater'));
|
||||||
|
});
|
||||||
|
|
||||||
|
const btnUploadKedatangan = form.querySelector('.tps-btn-upload-kedatangan');
|
||||||
|
if (btnUploadKedatangan) {
|
||||||
|
btnUploadKedatangan.addEventListener('click', function() {
|
||||||
|
uploadFotoKedatangan();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const btnUploadPetugas = form.querySelector('.tps-btn-upload-petugas');
|
||||||
|
if (btnUploadPetugas) {
|
||||||
|
btnUploadPetugas.addEventListener('click', function() {
|
||||||
|
uploadFotoPetugas();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
form.addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
submitTpsData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreTpsTimbanganItems() {
|
||||||
|
const tps = tpsData[activeTpsIndex];
|
||||||
|
const form = tpsContentContainer.querySelector('form');
|
||||||
|
const repeater = form.querySelector('.tps-timbangan-repeater');
|
||||||
|
|
||||||
|
if (tps.timbangan.length === 0) {
|
||||||
|
createTimbanganItem(repeater);
|
||||||
|
} else {
|
||||||
|
tps.timbangan.forEach(timb => {
|
||||||
|
const item = createTimbanganItem(repeater, timb);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateWaktuKedatangan() {
|
||||||
|
const tps = tpsData[activeTpsIndex];
|
||||||
|
const now = new Date();
|
||||||
|
const formatted = now.toLocaleString('id-ID', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
year: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit'
|
||||||
|
});
|
||||||
|
tps.waktuKedatangan = formatted;
|
||||||
|
|
||||||
|
const form = tpsContentContainer.querySelector('form');
|
||||||
|
const displayWaktu = form.querySelector('.tps-waktu-kedatangan');
|
||||||
|
if (displayWaktu) displayWaktu.value = formatted;
|
||||||
|
|
||||||
|
getLocationUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function reverseGeocode(lat, lng) {
|
||||||
|
const tps = tpsData[activeTpsIndex];
|
||||||
|
fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
const address = data.display_name || `${lat}, ${lng}`;
|
||||||
|
tps.latitude = lat;
|
||||||
|
tps.longitude = lng;
|
||||||
|
tps.alamatJalan = address;
|
||||||
|
|
||||||
|
const form = tpsContentContainer.querySelector('form');
|
||||||
|
if (form) {
|
||||||
|
const latInput = form.querySelector('.tps-display-latitude');
|
||||||
|
const lngInput = form.querySelector('.tps-display-longitude');
|
||||||
|
if (latInput) latInput.value = lat;
|
||||||
|
if (lngInput) lngInput.value = lng;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
tps.latitude = lat;
|
||||||
|
tps.longitude = lng;
|
||||||
|
tps.alamatJalan = `${lat}, ${lng}`;
|
||||||
|
|
||||||
|
const form = tpsContentContainer.querySelector('form');
|
||||||
|
if (form) {
|
||||||
|
const latInput = form.querySelector('.tps-display-latitude');
|
||||||
|
const lngInput = form.querySelector('.tps-display-longitude');
|
||||||
|
if (latInput) latInput.value = lat;
|
||||||
|
if (lngInput) lngInput.value = lng;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocationUpdate() {
|
||||||
|
if (!('geolocation' in navigator)) return;
|
||||||
|
|
||||||
|
navigator.geolocation.getCurrentPosition(
|
||||||
|
function(position) {
|
||||||
|
const lat = position.coords.latitude.toFixed(6);
|
||||||
|
const lng = position.coords.longitude.toFixed(6);
|
||||||
|
reverseGeocode(lat, lng);
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
console.log('Lokasi tidak diizinkan');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatFileSize(bytes) {
|
||||||
|
const mb = bytes / (1024 * 1024);
|
||||||
|
return `${mb.toFixed(2)} MB`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateMultiPreview(input, previewContainer) {
|
||||||
|
if (!input || !previewContainer) return;
|
||||||
|
|
||||||
|
previewContainer.innerHTML = '';
|
||||||
|
previewContainer.className = 'space-y-2';
|
||||||
|
|
||||||
|
if (!input.files || input.files.length === 0) return;
|
||||||
|
|
||||||
|
Array.from(input.files).forEach((file, index) => {
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.className = 'rounded-xl border border-gray-200 overflow-hidden bg-black';
|
||||||
|
|
||||||
|
const imageUrl = URL.createObjectURL(file);
|
||||||
|
const safeName = file.name.replace(/"/g, '"');
|
||||||
|
item.innerHTML = `
|
||||||
|
<div class="h-44 bg-black/80">
|
||||||
|
<img src="${imageUrl}" alt="Preview ${index + 1}" class="w-full h-full object-contain preview-multi-image" />
|
||||||
|
</div>
|
||||||
|
<div class="px-2 py-1 bg-white">
|
||||||
|
<p class="text-[11px] font-semibold text-gray-700 truncate">${index + 1}. ${safeName}</p>
|
||||||
|
<p class="text-[10px] text-gray-500">${formatFileSize(file.size)}</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const img = item.querySelector('.preview-multi-image');
|
||||||
|
if (img) {
|
||||||
|
img.onload = function() {
|
||||||
|
URL.revokeObjectURL(imageUrl);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
previewContainer.appendChild(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatWeightDisplay(value) {
|
||||||
|
if (isNaN(value)) return '0,00';
|
||||||
|
return value.toFixed(2).replace('.', ',');
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseWeightInput(value) {
|
||||||
|
if (!value) return 0;
|
||||||
|
const cleaned = value.toString().trim().replace(/\s/g, '').replace(',', '.');
|
||||||
|
const parsed = parseFloat(cleaned);
|
||||||
|
return isNaN(parsed) ? 0 : parsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readFileAsImage(file) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
const objectUrl = URL.createObjectURL(file);
|
||||||
|
const img = new Image();
|
||||||
|
img.onload = function() {
|
||||||
|
URL.revokeObjectURL(objectUrl);
|
||||||
|
resolve(img);
|
||||||
|
};
|
||||||
|
img.onerror = function() {
|
||||||
|
URL.revokeObjectURL(objectUrl);
|
||||||
|
reject(new Error('Gagal membaca gambar.'));
|
||||||
|
};
|
||||||
|
img.src = objectUrl;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCropCanvas(img, area) {
|
||||||
|
const sx = Math.max(0, Math.floor(img.width * area.x));
|
||||||
|
const sy = Math.max(0, Math.floor(img.height * area.y));
|
||||||
|
const sw = Math.max(1, Math.floor(img.width * area.w));
|
||||||
|
const sh = Math.max(1, Math.floor(img.height * area.h));
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const scale = 2.2;
|
||||||
|
canvas.width = Math.max(1, Math.floor(sw * scale));
|
||||||
|
canvas.height = Math.max(1, Math.floor(sh * scale));
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (!ctx) return canvas;
|
||||||
|
|
||||||
|
ctx.imageSmoothingEnabled = true;
|
||||||
|
ctx.drawImage(img, sx, sy, sw, sh, 0, 0, canvas.width, canvas.height);
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
function canvasToJpegFile(canvas, fileName) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
canvas.toBlob(function(blob) {
|
||||||
|
if (!blob) {
|
||||||
|
reject(new Error('Gagal membuat crop image.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve(new File([blob], fileName, { type: 'image/jpeg' }));
|
||||||
|
}, 'image/jpeg', 0.92);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function requestOpenRouterWeight(imageFile) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('Foto', imageFile);
|
||||||
|
const response = await fetch('/upst/detail-penjemputan/ocr-timbangan', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
const result = await response.json();
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(result.message || 'Request OCR gagal.');
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function autoFillWeight(file, weightInput, ocrInfoEl) {
|
||||||
|
let guessedWeight = 0;
|
||||||
|
weightInput.placeholder = 'Membaca angka dari foto...';
|
||||||
|
if (ocrInfoEl) ocrInfoEl.textContent = 'AI OCR: memproses gambar...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const img = await readFileAsImage(file);
|
||||||
|
let bestRawText = '';
|
||||||
|
let isSuccess = false;
|
||||||
|
|
||||||
|
for (const area of OCR_AREAS) {
|
||||||
|
const cropCanvas = createCropCanvas(img, area);
|
||||||
|
const cropFile = await canvasToJpegFile(cropCanvas, `crop-${area.id}.jpg`);
|
||||||
|
const aiResult = await requestOpenRouterWeight(cropFile);
|
||||||
|
|
||||||
|
if (aiResult && aiResult.success && aiResult.weight) {
|
||||||
|
guessedWeight = parseWeightInput(aiResult.weight);
|
||||||
|
bestRawText = aiResult.raw || aiResult.weight;
|
||||||
|
isSuccess = guessedWeight > 0;
|
||||||
|
if (isSuccess) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aiResult && aiResult.raw) {
|
||||||
|
bestRawText = aiResult.raw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ocrInfoEl) {
|
||||||
|
const cleaned = (bestRawText || '').replace(/\s+/g, ' ').trim();
|
||||||
|
ocrInfoEl.textContent = isSuccess
|
||||||
|
? `AI OCR terbaca: ${cleaned}`
|
||||||
|
: (cleaned ? `AI OCR tidak valid: ${cleaned}` : 'AI OCR tidak menemukan angka valid.');
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
guessedWeight = 0;
|
||||||
|
if (ocrInfoEl) ocrInfoEl.textContent = 'AI OCR gagal diproses.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (guessedWeight > 0) {
|
||||||
|
weightInput.value = formatWeightDisplay(guessedWeight);
|
||||||
|
weightInput.placeholder = 'Berat terdeteksi otomatis';
|
||||||
|
} else {
|
||||||
|
weightInput.placeholder = 'Tidak terbaca otomatis, isi manual';
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTpsTotalTimbangan();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTpsTotalTimbangan() {
|
||||||
|
const tps = tpsData[activeTpsIndex];
|
||||||
|
const form = tpsContentContainer.querySelector('form');
|
||||||
|
if (!form) return;
|
||||||
|
|
||||||
|
let total = 0.0;
|
||||||
|
const repeater = form.querySelector('.tps-timbangan-repeater');
|
||||||
|
const items = repeater.querySelectorAll('.timbangan-item');
|
||||||
|
|
||||||
|
items.forEach(function(item) {
|
||||||
|
const weightInput = item.querySelector('.input-berat-timbangan-value');
|
||||||
|
if (weightInput) {
|
||||||
|
const value = parseWeightInput(weightInput.value || '0');
|
||||||
|
total += value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tps.totalTimbangan = total;
|
||||||
|
|
||||||
|
const displayTotal = form.querySelector('.tps-display-total');
|
||||||
|
if (displayTotal) displayTotal.textContent = formatWeightDisplay(total);
|
||||||
|
|
||||||
|
if (grandTotalDisplay) {
|
||||||
|
grandTotalDisplay.textContent = formatWeightDisplay(total);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTimbanganItem(repeater, existingData = null) {
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.className = 'timbangan-item rounded-2xl border border-gray-200 p-3 space-y-2 bg-gray-50';
|
||||||
|
|
||||||
|
const weight = existingData ? existingData.weight : 0;
|
||||||
|
const hasFile = existingData && existingData.file;
|
||||||
|
const isUploaded = existingData && existingData.uploaded;
|
||||||
|
|
||||||
|
item.innerHTML = `
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<p class="text-xs font-bold text-gray-600">Item Timbangan</p>
|
||||||
|
<button type="button" class="btn-remove-timbangan text-[11px] font-bold text-red-500">Hapus</button>
|
||||||
|
</div>
|
||||||
|
<input type="file" name="FotoTimbangan" accept="image/*" class="input-foto-timbangan block w-full text-sm text-gray-700 border border-gray-200 rounded-xl p-2 file:mr-3 file:rounded-lg file:border-0 file:bg-upst file:px-3 file:py-2 file:text-xs file:font-bold file:text-white" />
|
||||||
|
<div class="${hasFile ? '' : 'hidden'} input-preview-wrap relative rounded-xl overflow-hidden border border-gray-200 bg-black">
|
||||||
|
<img class="input-preview-image w-full h-44 object-contain" alt="Preview foto timbangan" />
|
||||||
|
<div class="input-crop-overlay absolute inset-0 pointer-events-none"></div>
|
||||||
|
</div>
|
||||||
|
<p class="text-[11px] text-gray-500 input-ocr-info">${hasFile ? 'OCR: diproses.' : 'OCR: belum diproses.'}</p>
|
||||||
|
${hasFile && !isUploaded ? `
|
||||||
|
<button type="button" class="btn-upload-timbangan w-full bg-blue-500 text-white py-2 rounded-xl font-bold text-xs hover:brightness-110">
|
||||||
|
Upload Foto Timbangan Ini
|
||||||
|
</button>
|
||||||
|
` : isUploaded ? `
|
||||||
|
<div class="text-center text-xs text-green-600 font-bold py-2">
|
||||||
|
✓ Foto timbangan sudah diupload
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
<div class="grid grid-cols-1 gap-2">
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-semibold text-gray-600 mb-1">Berat (kg)</label>
|
||||||
|
<input type="text" inputmode="decimal" class="input-berat-timbangan-display w-full rounded-xl border border-gray-200 px-3 py-2 text-sm" placeholder="Contoh: 54,45" value="${weight > 0 ? formatWeightDisplay(weight) : ''}" />
|
||||||
|
<input type="hidden" class="input-berat-timbangan-value" value="${weight.toFixed(2)}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const fileInput = item.querySelector('.input-foto-timbangan');
|
||||||
|
const previewWrap = item.querySelector('.input-preview-wrap');
|
||||||
|
const previewImage = item.querySelector('.input-preview-image');
|
||||||
|
const cropOverlay = item.querySelector('.input-crop-overlay');
|
||||||
|
const ocrInfoEl = item.querySelector('.input-ocr-info');
|
||||||
|
const weightInputDisplay = item.querySelector('.input-berat-timbangan-display');
|
||||||
|
const weightInputValue = item.querySelector('.input-berat-timbangan-value');
|
||||||
|
const removeBtn = item.querySelector('.btn-remove-timbangan');
|
||||||
|
|
||||||
|
if (existingData && existingData.file) {
|
||||||
|
const localUrl = URL.createObjectURL(existingData.file);
|
||||||
|
previewImage.src = localUrl;
|
||||||
|
previewImage.onload = function() {
|
||||||
|
URL.revokeObjectURL(localUrl);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fileInput.addEventListener('change', async function() {
|
||||||
|
if (fileInput.files && fileInput.files[0]) {
|
||||||
|
const localUrl = URL.createObjectURL(fileInput.files[0]);
|
||||||
|
previewImage.src = localUrl;
|
||||||
|
previewWrap.classList.remove('hidden');
|
||||||
|
previewImage.onload = function() {
|
||||||
|
URL.revokeObjectURL(localUrl);
|
||||||
|
};
|
||||||
|
|
||||||
|
await autoFillWeight(fileInput.files[0], weightInputDisplay, ocrInfoEl);
|
||||||
|
const parsed = parseWeightInput(weightInputDisplay.value);
|
||||||
|
weightInputValue.value = parsed.toFixed(2);
|
||||||
|
updateTpsTotalTimbangan();
|
||||||
|
syncTimbanganToTpsData();
|
||||||
|
|
||||||
|
const tps = tpsData[activeTpsIndex];
|
||||||
|
const itemIndex = Array.from(repeater.children).indexOf(item);
|
||||||
|
if (itemIndex >= 0 && tps.timbangan[itemIndex]) {
|
||||||
|
tps.timbangan[itemIndex].uploaded = false;
|
||||||
|
|
||||||
|
const existingUploadBtn = item.querySelector('.btn-upload-timbangan');
|
||||||
|
if (!existingUploadBtn) {
|
||||||
|
const ocrInfo = item.querySelector('.input-ocr-info');
|
||||||
|
const uploadBtn = document.createElement('button');
|
||||||
|
uploadBtn.type = 'button';
|
||||||
|
uploadBtn.className = 'btn-upload-timbangan w-full bg-blue-500 text-white py-2 rounded-xl font-bold text-xs hover:brightness-110';
|
||||||
|
uploadBtn.textContent = 'Upload Foto Timbangan Ini';
|
||||||
|
uploadBtn.addEventListener('click', function() {
|
||||||
|
uploadSingleFotoTimbangan(itemIndex);
|
||||||
|
});
|
||||||
|
ocrInfo.parentNode.insertBefore(uploadBtn, ocrInfo.nextSibling);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
weightInputDisplay.addEventListener('input', function() {
|
||||||
|
const cleaned = this.value.replace(/[^0-9.,]/g, '');
|
||||||
|
this.value = cleaned;
|
||||||
|
const parsed = parseWeightInput(cleaned);
|
||||||
|
weightInputValue.value = parsed.toFixed(2);
|
||||||
|
updateTpsTotalTimbangan();
|
||||||
|
syncTimbanganToTpsData();
|
||||||
|
});
|
||||||
|
|
||||||
|
weightInputDisplay.addEventListener('blur', function() {
|
||||||
|
const parsed = parseWeightInput(this.value);
|
||||||
|
if (parsed > 0) {
|
||||||
|
this.value = formatWeightDisplay(parsed);
|
||||||
|
weightInputValue.value = parsed.toFixed(2);
|
||||||
|
} else {
|
||||||
|
this.value = '';
|
||||||
|
weightInputValue.value = '0.00';
|
||||||
|
}
|
||||||
|
updateTpsTotalTimbangan();
|
||||||
|
syncTimbanganToTpsData();
|
||||||
|
});
|
||||||
|
|
||||||
|
removeBtn.addEventListener('click', function() {
|
||||||
|
item.remove();
|
||||||
|
const form = tpsContentContainer.querySelector('form');
|
||||||
|
const repeater = form ? form.querySelector('.tps-timbangan-repeater') : null;
|
||||||
|
if (repeater && repeater.children.length === 0) {
|
||||||
|
createTimbanganItem(repeater);
|
||||||
|
}
|
||||||
|
updateTpsTotalTimbangan();
|
||||||
|
syncTimbanganToTpsData();
|
||||||
|
});
|
||||||
|
|
||||||
|
const btnUploadTimbangan = item.querySelector('.btn-upload-timbangan');
|
||||||
|
if (btnUploadTimbangan) {
|
||||||
|
btnUploadTimbangan.addEventListener('click', function() {
|
||||||
|
const itemIndex = Array.from(repeater.children).indexOf(item);
|
||||||
|
uploadSingleFotoTimbangan(itemIndex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
repeater.appendChild(item);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncTimbanganToTpsData() {
|
||||||
|
const tps = tpsData[activeTpsIndex];
|
||||||
|
const form = tpsContentContainer.querySelector('form');
|
||||||
|
if (!form) return;
|
||||||
|
|
||||||
|
const repeater = form.querySelector('.tps-timbangan-repeater');
|
||||||
|
const items = repeater.querySelectorAll('.timbangan-item');
|
||||||
|
|
||||||
|
tps.timbangan = [];
|
||||||
|
items.forEach(item => {
|
||||||
|
const fileInput = item.querySelector('.input-foto-timbangan');
|
||||||
|
const weightValue = item.querySelector('.input-berat-timbangan-value');
|
||||||
|
|
||||||
|
const existingIndex = tps.timbangan.length;
|
||||||
|
const existingData = tps.timbangan[existingIndex];
|
||||||
|
|
||||||
|
tps.timbangan.push({
|
||||||
|
file: fileInput.files[0] || (existingData ? existingData.file : null),
|
||||||
|
weight: parseWeightInput(weightValue.value),
|
||||||
|
uploaded: existingData ? existingData.uploaded : false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadSingleFotoTimbangan(itemIndex) {
|
||||||
|
const tps = tpsData[activeTpsIndex];
|
||||||
|
|
||||||
|
if (!tps.timbangan[itemIndex] || !tps.timbangan[itemIndex].file) {
|
||||||
|
alert('Belum ada foto timbangan yang dipilih!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const timbanganItem = tps.timbangan[itemIndex];
|
||||||
|
|
||||||
|
alert(`Upload foto timbangan #${itemIndex + 1}\nBerat: ${timbanganItem.weight} kg\n(Implementasi upload ke server)`);
|
||||||
|
|
||||||
|
timbanganItem.uploaded = true;
|
||||||
|
|
||||||
|
const form = tpsContentContainer.querySelector('form');
|
||||||
|
const repeater = form.querySelector('.tps-timbangan-repeater');
|
||||||
|
const items = repeater.querySelectorAll('.timbangan-item');
|
||||||
|
const targetItem = items[itemIndex];
|
||||||
|
|
||||||
|
if (targetItem) {
|
||||||
|
const uploadBtn = targetItem.querySelector('.btn-upload-timbangan');
|
||||||
|
if (uploadBtn) {
|
||||||
|
uploadBtn.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const ocrInfo = targetItem.querySelector('.input-ocr-info');
|
||||||
|
if (ocrInfo && !targetItem.querySelector('.upload-success-message')) {
|
||||||
|
const successMsg = document.createElement('div');
|
||||||
|
successMsg.className = 'text-center text-xs text-green-600 font-bold py-2 upload-success-message';
|
||||||
|
successMsg.textContent = '✓ Foto timbangan sudah diupload';
|
||||||
|
ocrInfo.parentNode.insertBefore(successMsg, ocrInfo.nextSibling);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadFotoKedatangan() {
|
||||||
|
const tps = tpsData[activeTpsIndex];
|
||||||
|
if (tps.fotoKedatangan.length === 0) {
|
||||||
|
alert('Belum ada foto kedatangan yang dipilih!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
alert(`Upload ${tps.fotoKedatangan.length} foto kedatangan\n(Implementasi upload ke server)`);
|
||||||
|
|
||||||
|
tps.fotoKedatanganUploaded = true;
|
||||||
|
renderTpsForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadFotoPetugas() {
|
||||||
|
const tps = tpsData[activeTpsIndex];
|
||||||
|
if (tps.fotoPetugas.length === 0) {
|
||||||
|
alert('Belum ada foto petugas yang dipilih!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
alert(`Upload ${tps.fotoPetugas.length} foto petugas\n(Implementasi upload ke server)`);
|
||||||
|
|
||||||
|
tps.fotoPetugasUploaded = true;
|
||||||
|
renderTpsForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitTpsData() {
|
||||||
|
const tps = tpsData[activeTpsIndex];
|
||||||
|
|
||||||
|
if (!tps.fotoKedatangan.length) {
|
||||||
|
alert('Foto kedatangan belum diupload!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!tps.timbangan.length) {
|
||||||
|
alert('Belum ada data timbangan!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!tps.fotoPetugas.length) {
|
||||||
|
alert('Foto petugas belum diupload!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!tps.namaPetugas.trim()) {
|
||||||
|
alert('Nama petugas belum diisi!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('Latitude', tps.latitude);
|
||||||
|
formData.append('Longitude', tps.longitude);
|
||||||
|
formData.append('AlamatJalan', tps.alamatJalan);
|
||||||
|
formData.append('WaktuKedatangan', tps.waktuKedatangan);
|
||||||
|
formData.append('TotalTimbangan', tps.totalTimbangan);
|
||||||
|
formData.append('NamaPetugas', tps.namaPetugas);
|
||||||
|
|
||||||
|
tps.fotoKedatangan.forEach((file, i) => {
|
||||||
|
formData.append(`FotoKedatangan`, file);
|
||||||
|
});
|
||||||
|
|
||||||
|
tps.timbangan.forEach((timb, i) => {
|
||||||
|
if (timb.file) formData.append(`FotoTimbangan`, timb.file);
|
||||||
|
formData.append(`BeratTimbangan`, timb.weight);
|
||||||
|
});
|
||||||
|
|
||||||
|
tps.fotoPetugas.forEach((file, i) => {
|
||||||
|
formData.append(`FotoPetugas`, file);
|
||||||
|
});
|
||||||
|
|
||||||
|
alert(`Submit data pengangkutan:\n- Lokasi: ${tps.alamatJalan}\n- Total: ${tps.totalTimbangan} kg\n- Petugas: ${tps.namaPetugas}\n\n(Implementasi POST ke server)`);
|
||||||
|
|
||||||
|
tps.submitted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeLocation();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</register-block>
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Admin/Transport/SpjDriverUpst/Shared/_Layout.cshtml";
|
||||||
|
ViewData["Title"] = "Detail Perjalanan - DLH";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="w-full lg:max-w-sm mx-auto bg-gray-50 min-h-screen">
|
||||||
|
<div class="bg-upst text-white px-6 pt-10 pb-16 rounded-b-[40px] shadow-lg relative overflow-hidden">
|
||||||
|
<div class="absolute top-0 right-0 w-32 h-32 bg-white/5 rounded-full -mr-16 -mt-16"></div>
|
||||||
|
<div class="flex items-center justify-between relative z-10">
|
||||||
|
<a href="@Url.Action("Index", "History")" class="w-10 h-10 flex items-center justify-center bg-white/20 hover:bg-white/30 rounded-xl transition-colors">
|
||||||
|
<i class="w-5 h-5" data-lucide="arrow-left"></i>
|
||||||
|
</a>
|
||||||
|
<div class="text-center">
|
||||||
|
<h1 class="text-lg font-bold tracking-wide uppercase">Detail SPJ</h1>
|
||||||
|
<p class="text-[10px] text-white/70 font-medium">Informasi Lengkap Perjalanan</p>
|
||||||
|
</div>
|
||||||
|
<div class="w-10"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="px-5 -mt-8 space-y-5 relative z-20">
|
||||||
|
<div class="bg-white rounded-3xl p-6 border border-gray-100">
|
||||||
|
<div class="flex flex-col items-center text-center mb-6">
|
||||||
|
<span class="text-[10px] font-black text-gray-400 uppercase tracking-[0.2em] mb-1">Nomor SPJ</span>
|
||||||
|
<h2 class="text-lg font-black text-gray-900 leading-tight">SPJ/07-2025/PKM/000476</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-4 py-4 border-t border-b border-gray-50">
|
||||||
|
<div class="space-y-1">
|
||||||
|
<span class="text-[10px] font-bold text-gray-400 uppercase">Kendaraan</span>
|
||||||
|
<p class="font-black text-gray-800 text-sm">B 9632 TOR</p>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-1 text-right">
|
||||||
|
<span class="text-[10px] font-bold text-gray-400 uppercase">No. Pintu</span>
|
||||||
|
<p class="font-mono font-bold text-gray-700 text-sm">JRC 005</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pt-4 space-y-1">
|
||||||
|
<span class="text-[10px] font-bold text-gray-400 uppercase">Tujuan Akhir</span>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<i class="w-4 h-4 text-upst" data-lucide="map-pin"></i>
|
||||||
|
<p class="font-bold text-gray-800">Taman Barito</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex divide-x divide-gray-100 border-b bg-white rounded-xl border-gray-100 py-3">
|
||||||
|
<div class="flex-1 text-center">
|
||||||
|
<p class="text-[10px] font-bold text-gray-400 uppercase">Lokasi</p>
|
||||||
|
<p class="text-sm font-black text-gray-900">3</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 text-center">
|
||||||
|
<p class="text-[10px] font-bold text-green-500 uppercase">Selesai</p>
|
||||||
|
<p class="text-sm font-black text-green-600">1</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 text-center">
|
||||||
|
<p class="text-[10px] font-bold text-yellow-500 uppercase">Proses</p>
|
||||||
|
<p class="text-sm font-black text-yellow-600">1</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 text-center">
|
||||||
|
<p class="text-[10px] font-bold text-red-500 uppercase">Batal</p>
|
||||||
|
<p class="text-sm font-black text-red-600">1</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-4 space-y-4 bg-white rounded-3xl border border-gray-100">
|
||||||
|
<h3 class="text-[11px] font-black text-gray-400 uppercase tracking-widest mb-2 px-1">Riwayat Lokasi Pengangkutan</h3>
|
||||||
|
|
||||||
|
<div class="flex gap-4 group">
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<div class="w-6 h-6 rounded-full bg-yellow-500 flex items-center justify-center text-white ring-4 ring-yellow-50">
|
||||||
|
<i class="w-3 h-3" data-lucide="clock"></i>
|
||||||
|
</div>
|
||||||
|
<div class="w-0.5 h-full bg-gray-100 my-1"></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 pb-4">
|
||||||
|
<div class="flex justify-between items-start mb-1">
|
||||||
|
<h4 class="text-sm font-black text-gray-800 uppercase leading-none">CV Tri Mitra Utama</h4>
|
||||||
|
<span class="text-[9px] font-bold text-yellow-600 uppercase">Proses</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-gray-500 leading-snug">Shell Radio Dalam, Jl. Radio Dalam Raya No.6C, Kebayoran Baru</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-4 group">
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<div class="w-6 h-6 rounded-full bg-green-500 flex items-center justify-center text-white ring-4 ring-green-50">
|
||||||
|
<i class="w-3 h-3" data-lucide="check"></i>
|
||||||
|
</div>
|
||||||
|
<div class="w-0.5 h-full bg-gray-100 my-1"></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 pb-4">
|
||||||
|
<div class="flex justify-between items-start mb-1">
|
||||||
|
<h4 class="text-sm font-black text-gray-800 uppercase leading-none">CV Tri Berkah Sejahtera</h4>
|
||||||
|
<span class="text-[9px] font-bold text-green-600 uppercase">Selesai</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-gray-500 leading-snug">Kp. Pertanian II Rt.004 Rw.001, Duren Sawit, Jakarta Timur</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-4 group">
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<div class="w-6 h-6 rounded-full bg-red-500 flex items-center justify-center text-white ring-4 ring-red-50">
|
||||||
|
<i class="w-3 h-3" data-lucide="x"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex justify-between items-start mb-1">
|
||||||
|
<h4 class="text-sm font-black text-gray-800 uppercase leading-none text-gray-400">CV Tri Berkah Sejahtera</h4>
|
||||||
|
<span class="text-[9px] font-bold text-red-600 uppercase">Batal</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-gray-400 leading-snug italic">Titik pengangkutan dibatalkan oleh sistem.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Bottom Navigation -->
|
||||||
|
<partial name="~/Views/Admin/Transport/SpjDriverUpst/Shared/Components/_Navigation.cshtml" />
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Admin/Transport/SpjDriverUpst/Shared/_Layout.cshtml";
|
||||||
|
ViewData["Title"] = "History - DLH";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="w-full lg:max-w-sm mx-auto bg-gray-50 min-h-screen">
|
||||||
|
<div class="bg-upst text-white px-6 pt-10 pb-16 rounded-b-[40px] shadow-lg relative overflow-hidden">
|
||||||
|
<div class="absolute top-0 right-0 w-32 h-32 bg-white/5 rounded-full -mr-16 -mt-16"></div>
|
||||||
|
<div class="flex items-center justify-between relative z-10">
|
||||||
|
<a href="@Url.Action("Index", "History")" class="w-10 h-10 flex items-center justify-center bg-white/20 hover:bg-white/30 rounded-xl transition-colors">
|
||||||
|
<i class="w-5 h-5" data-lucide="arrow-left"></i>
|
||||||
|
</a>
|
||||||
|
<div class="text-center">
|
||||||
|
<h1 class="text-lg font-bold tracking-wide uppercase">History Perjalanan</h1>
|
||||||
|
<p class="text-[10px] text-white/70 font-medium">Riwayat Lengkap Perjalanan</p>
|
||||||
|
</div>
|
||||||
|
<img src="@Url.Content("~/driver/upst_white.svg")" alt="UPST Logo" class="absolute top-6 left-8 w-20 h-auto opacity-20">
|
||||||
|
<div class="w-10"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@{
|
||||||
|
var spjList = new[]
|
||||||
|
{
|
||||||
|
new {
|
||||||
|
Id = 1,
|
||||||
|
NoSpj = "SPJ/07-2025/PKM/000478",
|
||||||
|
Plat = "B 5678 ABC",
|
||||||
|
Kode = "JRC 007",
|
||||||
|
Tujuan = "Bantar Gebang",
|
||||||
|
Status = "In Progress",
|
||||||
|
Tanggal = "28 Jul 2025",
|
||||||
|
Waktu = "16:45"
|
||||||
|
},
|
||||||
|
new {
|
||||||
|
Id = 2,
|
||||||
|
NoSpj = "SPJ/07-2025/PKM/000476",
|
||||||
|
Plat = "B 9632 TOR",
|
||||||
|
Kode = "JRC 005",
|
||||||
|
Tujuan = "RDF Rorotan",
|
||||||
|
Status = "Completed",
|
||||||
|
Tanggal = "27 Jul 2025",
|
||||||
|
Waktu = "14:30"
|
||||||
|
},
|
||||||
|
new {
|
||||||
|
Id = 3,
|
||||||
|
NoSpj = "SPJ/07-2025/PKM/000477",
|
||||||
|
Plat = "B 1234 XYZ",
|
||||||
|
Kode = "JRC 006",
|
||||||
|
Tujuan = "RDF Pesanggarahan",
|
||||||
|
Status = "Completed",
|
||||||
|
Tanggal = "26 Jul 2025",
|
||||||
|
Waktu = "09:15"
|
||||||
|
},
|
||||||
|
new {
|
||||||
|
Id = 4,
|
||||||
|
NoSpj = "SPJ/07-2025/PKM/000479",
|
||||||
|
Plat = "B 9876 DEF",
|
||||||
|
Kode = "JRC 008",
|
||||||
|
Tujuan = "RDF Sunter",
|
||||||
|
Status = "Completed",
|
||||||
|
Tanggal = "25 Jul 2025",
|
||||||
|
Waktu = "11:20"
|
||||||
|
},
|
||||||
|
new {
|
||||||
|
Id = 5,
|
||||||
|
NoSpj = "SPJ/07-2025/PKM/000480",
|
||||||
|
Plat = "B 4321 GHI",
|
||||||
|
Kode = "JRC 009",
|
||||||
|
Tujuan = "Bantar Gebang",
|
||||||
|
Status = "Completed",
|
||||||
|
Tanggal = "24 Jul 2025",
|
||||||
|
Waktu = "08:45"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="px-5 -mt-8 space-y-5 relative z-20">
|
||||||
|
@foreach (var spj in spjList)
|
||||||
|
{
|
||||||
|
<a href="@Url.Action("Details", "History", new { id = spj.Id })" class="block group">
|
||||||
|
<div class="bg-white rounded-3xl p-5 shadow-sm border border-gray-100 group-hover:shadow-md group-hover:-translate-y-1 transition-all duration-300">
|
||||||
|
|
||||||
|
<div class="flex justify-between items-start mb-4">
|
||||||
|
<div class="space-y-0.5">
|
||||||
|
<p class="text-[10px] font-bold text-gray-400 uppercase tracking-widest">Nomor Dokumen</p>
|
||||||
|
<p class="text-sm font-black text-gray-800">@spj.NoSpj</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (spj.Status == "Completed")
|
||||||
|
{
|
||||||
|
<span class="bg-green-50 text-green-600 px-3 py-1 rounded-lg text-[10px] font-black uppercase border border-green-100">
|
||||||
|
Selesai
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="bg-blue-50 text-blue-600 px-3 py-1 rounded-lg text-[10px] font-black uppercase border border-blue-100 animate-pulse">
|
||||||
|
Proses
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-4 bg-gray-50/80 p-3 rounded-2xl border border-dashed border-gray-200">
|
||||||
|
<div class="w-12 h-12 bg-upst rounded-xl flex items-center justify-center shadow-inner">
|
||||||
|
<i class="w-6 h-6 text-white" data-lucide="truck"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="flex justify-between items-baseline">
|
||||||
|
<h2 class="font-black text-gray-900 leading-tight">@spj.Plat</h2>
|
||||||
|
<span class="text-[11px] font-bold text-upst">@spj.Waktu</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<span class="text-xs text-gray-500 font-medium">@spj.Kode</span>
|
||||||
|
<span class="text-[10px] text-gray-400 font-semibold">@spj.Tanggal</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<div class="w-2 h-2 rounded-full bg-upst/40"></div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="text-[10px] text-gray-400 font-bold uppercase">Tujuan Akhir</span>
|
||||||
|
<span class="text-sm font-bold text-gray-700">@spj.Tujuan</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-8 h-8 rounded-full bg-gray-100 flex items-center justify-center group-hover:bg-upst transition-colors">
|
||||||
|
<i class="w-4 h-4" data-lucide="chevron-right"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<partial name="~/Views/Admin/Transport/SpjDriverUpst/Shared/Components/_Navigation.cshtml" />
|
||||||
|
|
||||||
|
<!-- Kalau butuh tampilan kosong (jika tidak ada data) -->
|
||||||
|
|
||||||
|
@* <div class="flex flex-col items-center justify-center py-16 px-4">
|
||||||
|
<div class="w-24 h-24 bg-gray-100 rounded-full flex items-center justify-center mb-4">
|
||||||
|
<i class="w-12 h-12 text-gray-400" data-lucide="clock"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 mb-2">Belum Ada Riwayat</h3>
|
||||||
|
<p class="text-gray-500 text-center text-sm">Riwayat perjalanan Anda akan muncul di sini setelah melakukan perjalanan pertama.</p>
|
||||||
|
</div> *@
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,788 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Admin/Transport/SpjDriverUpst/Shared/_Layout.cshtml";
|
||||||
|
ViewData["Title"] = "Home Page";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="w-full lg:max-w-sm mx-auto bg-gray-50 min-h-screen pb-28 relative font-sans">
|
||||||
|
|
||||||
|
<div class="bg-upst text-white px-6 pt-10 pb-24 rounded-b-[50px] shadow-2xl relative overflow-hidden">
|
||||||
|
<img src="@Url.Content("~/driver/upst_white.svg")" alt="UPST Logo" class="absolute top-4 left-6 w-20 h-auto opacity-20">
|
||||||
|
<div class="absolute top-0 right-0 w-32 h-32 bg-white/5 rounded-full -mr-16 -mt-16"></div>
|
||||||
|
|
||||||
|
<div class="flex justify-between items-start relative z-10">
|
||||||
|
<div class="space-y-1">
|
||||||
|
<p class="text-[10px] pt-5 font-black uppercase tracking-[0.2em] text-orange-200 opacity-80">Akun Driver</p>
|
||||||
|
<h1 class="text-2xl font-extrabold tracking-tight">Bonny Agung</h1>
|
||||||
|
<div class="flex items-center gap-2 mt-2 bg-black/20 w-fit px-3 py-1 rounded-full border border-white/10">
|
||||||
|
<span class="w-2 h-2 bg-green-400 rounded-full animate-pulse"></span>
|
||||||
|
<span class="text-[10px] font-bold uppercase" id="currentDateTime">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="profileMenuButton" class="w-12 h-12 rounded-2xl bg-white/10 border border-white/20 backdrop-blur-lg flex items-center justify-center shadow-lg transition-transform active:scale-90">
|
||||||
|
<i class="w-6 h-6 text-white" data-lucide="user"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="profileMenuDropdown" class="absolute top-24 right-6 w-36 bg-white rounded-2xl shadow-2xl py-2 z-50 hidden border border-gray-100">
|
||||||
|
<form method="post" asp-controller="Auth" asp-action="Logout">
|
||||||
|
<button type="submit" class="flex items-center gap-3 w-full px-4 py-3 text-xs font-bold text-red-600 hover:bg-red-50 transition-colors">
|
||||||
|
<i class="w-4 h-4" data-lucide="log-out"></i> Logout
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="px-4 -mt-16 relative z-20">
|
||||||
|
<div class="bg-upst-light rounded-3xl p-4 border border-gray-100 mb-6">
|
||||||
|
<div class="flex items-center gap-4 group cursor-pointer" id="userLocationBtn">
|
||||||
|
<div class="w-10 h-10 bg-upst rounded-2xl flex items-center justify-center flex-shrink-0 group-active:scale-90 transition-transform">
|
||||||
|
<i class="w-5 h-5 text-white" data-lucide="map-pin"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<p class="text-[9px] font-black text-gray-400 uppercase tracking-wider leading-none mb-1">Lokasi Saat Ini</p>
|
||||||
|
<p id="userLocation" class="text-xs line-clamp-3 font-bold text-gray-700 italic">Mendeteksi lokasi...</p>
|
||||||
|
</div>
|
||||||
|
<i class="w-4 h-4 text-gray-700" data-lucide="refresh-cw"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-2 mb-4 p-1 bg-white rounded-2xl border border-gray-100 shadow-sm">
|
||||||
|
<button id="tabSpjButton" type="button" class="px-3 py-2 text-xs font-black rounded-xl bg-upst text-white transition-all">
|
||||||
|
SPJ
|
||||||
|
</button>
|
||||||
|
<button id="tabMapsButton" type="button" class="px-3 py-2 text-xs font-black rounded-xl text-gray-500 bg-gray-100 transition-all">
|
||||||
|
MAPS
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="spjCardPanel" class="bg-upst rounded-[35px] overflow-hidden shadow-2xl relative h-[300px]">
|
||||||
|
<div class="bg-white/10 backdrop-blur-md p-5 flex justify-between items-center border-b border-white/10">
|
||||||
|
<div>
|
||||||
|
<p class="text-[10px] font-black text-green-100 uppercase tracking-widest">Nomor SPJ</p>
|
||||||
|
<p class="text-white font-mono font-bold text-sm">SPJ/07-2025/PKM/000476</p>
|
||||||
|
</div>
|
||||||
|
<div class="text-right">
|
||||||
|
<span class="bg-white text-green-600 text-[10px] font-black px-3 py-1 rounded-lg shadow-sm">B 9632 TOR</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-6 relative h-[236px]">
|
||||||
|
<div class="flex justify-between items-center h-full">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<p class="text-[10px] text-green-100 font-bold uppercase opacity-80">Tujuan Pembuangan</p>
|
||||||
|
<h2 class="text-2xl font-black text-white tracking-tight leading-tight">JRC Rorotan</h2>
|
||||||
|
<p class="text-xs text-green-100/70 font-medium">(JRC 005)</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="qrCodeTrigger" class="w-16 h-16 bg-white rounded-[24px] flex items-center justify-center shadow-2xl border-4 border-green-600/20 active:scale-90 transition-transform">
|
||||||
|
<img src="@Url.Content("~/driver/images/qr.png")" alt="QR" class="w-10 h-10 object-contain">
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<img src="@Url.Content("~/driver/tree.svg")" class="absolute -left-4 -bottom-4 w-20 h-20 opacity-10 pointer-events-none" alt="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="mapsCardPanel" class="bg-upst rounded-[35px] overflow-hidden shadow-2xl relative h-[300px] flex-col hidden">
|
||||||
|
<div class="bg-white/10 backdrop-blur-md p-5 flex justify-between items-center border-b border-white/10">
|
||||||
|
<div>
|
||||||
|
<p class="text-[10px] font-black text-green-100 uppercase tracking-widest">Maps</p>
|
||||||
|
<p class="text-white font-mono font-bold text-sm">Rute Pengangkutan</p>
|
||||||
|
</div>
|
||||||
|
<div class="text-right">
|
||||||
|
<span class="bg-white text-green-600 text-[10px] font-black px-3 py-1 rounded-lg shadow-sm">LIVE</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-6 relative h-[236px]">
|
||||||
|
<div id="pickupMap" class="h-full w-full z-10 rounded-3xl overflow-hidden border border-white/20"></div>
|
||||||
|
<button id="recenterMapButton" type="button" class="absolute top-9 right-9 z-20 w-11 h-11 bg-white/95 text-upst rounded-2xl shadow-xl border border-white/70 backdrop-blur flex items-center justify-center active:scale-95 transition-transform" title="Tampilkan semua lokasi">
|
||||||
|
<i class="w-5 h-5" data-lucide="locate-fixed"></i>
|
||||||
|
</button>
|
||||||
|
<img src="@Url.Content("~/driver/tree.svg")" class="absolute -left-4 -bottom-4 w-20 h-20 opacity-10 pointer-events-none" alt="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="px-6 mt-10">
|
||||||
|
<div class="flex justify-between items-end mb-6">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-xl font-black text-gray-800 tracking-tighter">Lokasi</h3>
|
||||||
|
<p class="text-[10px] text-gray-400 font-bold uppercase">Pengangkutan</p>
|
||||||
|
</div>
|
||||||
|
<button id="addPickupButton" class="bg-upst text-white p-3 rounded-2xl shadow-lg shadow-gray-200 hover:brightness-110 active:scale-90 transition-all">
|
||||||
|
<i class="w-5 h-5" data-lucide="plus"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-6 relative">
|
||||||
|
<div class="absolute left-6 top-2 bottom-2 w-0.5 bg-gray-200 rounded-full"></div>
|
||||||
|
|
||||||
|
<a href="@Url.Action("Index", "DetailPenjemputan")" class="block relative pl-12 group">
|
||||||
|
<div class="absolute left-4 top-1 w-4 h-4 bg-white border-4 border-gray-400 rounded-full z-10"></div>
|
||||||
|
<div class="bg-white p-4 rounded-3xl border border-gray-100 shadow-sm group-active:bg-gray-50 transition-colors">
|
||||||
|
<div class="flex justify-between items-start mb-2">
|
||||||
|
<span class="text-[9px] font-black text-gray-400 uppercase tracking-widest">Proses</span>
|
||||||
|
<i class="w-4 h-4 text-gray-300" data-lucide="loader"></i>
|
||||||
|
</div>
|
||||||
|
<h4 class="font-bold text-gray-800 text-sm leading-tight">CV Tri Mitra Utama - Shell Radio Dalam</h4>
|
||||||
|
<div class="flex items-center gap-1 mt-2 text-gray-500">
|
||||||
|
<i class="w-3 h-3" data-lucide="map"></i>
|
||||||
|
<p class="text-[10px] truncate italic">Jakarta Timur 13470</p>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<span class="text-[9px] font-bold text-blue-600 bg-blue-50 px-2 py-1 rounded-lg">Ada 3 TPS</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="@Url.Action("TanpaTps", "DetailPenjemputan")" class="block relative pl-12 group">
|
||||||
|
<div class="absolute left-4 top-1 w-4 h-4 bg-green-500 rounded-full z-10 ring-4 ring-green-100"></div>
|
||||||
|
<div class="bg-green-50/50 p-4 rounded-3xl border border-green-100 shadow-sm group-active:bg-green-100 transition-colors">
|
||||||
|
<div class="flex justify-between items-start mb-2">
|
||||||
|
<span class="text-[9px] font-black text-green-600 uppercase tracking-widest italic tracking-tighter">Selesai</span>
|
||||||
|
<i class="w-4 h-4 text-green-500" data-lucide="check-circle-2"></i>
|
||||||
|
</div>
|
||||||
|
<h4 class="font-bold text-gray-800 text-sm leading-tight">CV Tri Berkah Sejahtera</h4>
|
||||||
|
<p class="text-[10px] text-green-700/60 mt-2">Duren Sawit, Jakarta Timur</p>
|
||||||
|
<div class="mt-2">
|
||||||
|
<span class="text-[9px] font-bold text-gray-600 bg-gray-100 px-2 py-1 rounded-lg">Tidak ada TPS</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="@Url.Action("Batal", "DetailPenjemputan")" class="block relative pl-12 group">
|
||||||
|
<div class="absolute left-4 top-1 w-4 h-4 bg-red-500 rounded-full z-10 ring-4 ring-red-100"></div>
|
||||||
|
<div class="bg-red-50/50 p-4 rounded-3xl border border-red-100 shadow-sm group-active:bg-red-100 transition-colors">
|
||||||
|
<div class="flex justify-between items-start mb-2">
|
||||||
|
<span class="text-[9px] font-black text-red-600 uppercase tracking-widest italic tracking-tighter">Batal</span>
|
||||||
|
<i class="w-4 h-4 text-red-500" data-lucide="x-circle"></i>
|
||||||
|
</div>
|
||||||
|
<h4 class="font-bold text-gray-800 text-sm leading-tight">CV Tri Berkah Sejahtera</h4>
|
||||||
|
<p class="text-[10px] text-red-700/60 mt-2">Klender, Jakarta Timur</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<partial name="~/Views/Admin/Transport/SpjDriverUpst/Shared/Components/_Navigation.cshtml" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="qrCodeModal" class="fixed inset-0 bg-upst/90 backdrop-blur-xl items-center justify-center z-[100] hidden p-8 transition-all">
|
||||||
|
<div class="w-full max-w-xs space-y-6">
|
||||||
|
<div class="bg-white rounded-[45px] p-8 shadow-2xl relative overflow-hidden">
|
||||||
|
<div class="absolute top-0 left-0 w-full h-2 bg-upst-light"></div>
|
||||||
|
|
||||||
|
<div class="text-center mb-6">
|
||||||
|
<p class="text-[10px] font-black text-gray-400 uppercase tracking-[0.3em] mb-1">Scanner Siap</p>
|
||||||
|
<h3 class="text-lg font-black text-gray-800">Verifikasi SPJ</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-gray-50 p-6 rounded-[35px] border-2 border-gray-100 shadow-inner">
|
||||||
|
<img src="@Url.Content("~/driver/images/qr.png")" alt="QR Code" class="w-full h-auto">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6 text-center">
|
||||||
|
<p class="text-[11px] font-mono text-gray-500 break-all bg-gray-100 py-2 px-3 rounded-xl">SPJ/07-2025/PKM/000476</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="closeQrModal" class="w-full py-4 bg-white/10 border border-white/20 rounded-2xl text-white font-bold text-sm backdrop-blur-md active:scale-95 transition-all">
|
||||||
|
TUTUP
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="addPickupModal" class="fixed inset-0 bg-upst/90 backdrop-blur-xl items-center justify-center z-[100] hidden p-8 transition-all">
|
||||||
|
<div class="w-full max-w-xs space-y-6">
|
||||||
|
<div class="bg-white rounded-[45px] p-8 shadow-2xl relative overflow-hidden">
|
||||||
|
<div class="absolute top-0 left-0 w-full h-2 bg-upst-light"></div>
|
||||||
|
|
||||||
|
<div class="text-center mb-6">
|
||||||
|
<p class="text-[10px] font-black text-gray-400 uppercase tracking-[0.3em] mb-1">Tambah Lokasi</p>
|
||||||
|
<h3 class="text-lg font-black text-gray-800">Pengangkutan</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="text-xs font-bold text-gray-600 uppercase tracking-wider">Pilih Lokasi Pengangkutan</label>
|
||||||
|
<select id="pickupSelect" class="w-full mt-2"></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6 text-center">
|
||||||
|
<button id="closeAddModal" class="w-full py-4 bg-upst text-white rounded-2xl font-bold text-sm active:scale-95 transition-all">
|
||||||
|
TUTUP
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<register-block dynamic-section="scripts" key="jsHomeIndex">
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const userLocationEl = document.getElementById("userLocation");
|
||||||
|
|
||||||
|
function reverseGeocode(lat, lng) {
|
||||||
|
fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
const address = data.display_name || `${lat}, ${lng}`;
|
||||||
|
userLocationEl.textContent = address;
|
||||||
|
localStorage.setItem("user_latitude", lat);
|
||||||
|
localStorage.setItem("user_longitude", lng);
|
||||||
|
localStorage.setItem("user_address", address);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
userLocationEl.textContent = `${lat}, ${lng}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocationUpdate() {
|
||||||
|
if ("geolocation" in navigator) {
|
||||||
|
userLocationEl.textContent = "Mendeteksi lokasi baru...";
|
||||||
|
navigator.geolocation.getCurrentPosition(
|
||||||
|
function (position) {
|
||||||
|
const lat = position.coords.latitude.toFixed(6);
|
||||||
|
const lng = position.coords.longitude.toFixed(6);
|
||||||
|
reverseGeocode(lat, lng);
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
userLocationEl.textContent = "Lokasi tidak diizinkan";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
userLocationEl.textContent = "Browser tidak mendukung lokasi";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const savedAddress = localStorage.getItem("user_address");
|
||||||
|
if (savedAddress) {
|
||||||
|
userLocationEl.textContent = savedAddress;
|
||||||
|
} else {
|
||||||
|
getLocationUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update Lokasi cuy
|
||||||
|
userLocationEl.addEventListener("click", function () {
|
||||||
|
getLocationUpdate();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const tabSpjButton = document.getElementById("tabSpjButton");
|
||||||
|
const tabMapsButton = document.getElementById("tabMapsButton");
|
||||||
|
const spjCardPanel = document.getElementById("spjCardPanel");
|
||||||
|
const mapsCardPanel = document.getElementById("mapsCardPanel");
|
||||||
|
|
||||||
|
if (!tabSpjButton || !tabMapsButton || !spjCardPanel || !mapsCardPanel) return;
|
||||||
|
|
||||||
|
function activateTab(tab) {
|
||||||
|
const isSpj = tab === "spj";
|
||||||
|
|
||||||
|
tabSpjButton.classList.toggle("bg-upst", isSpj);
|
||||||
|
tabSpjButton.classList.toggle("text-white", isSpj);
|
||||||
|
tabSpjButton.classList.toggle("bg-gray-100", !isSpj);
|
||||||
|
tabSpjButton.classList.toggle("text-gray-500", !isSpj);
|
||||||
|
|
||||||
|
tabMapsButton.classList.toggle("bg-upst", !isSpj);
|
||||||
|
tabMapsButton.classList.toggle("text-white", !isSpj);
|
||||||
|
tabMapsButton.classList.toggle("bg-gray-100", isSpj);
|
||||||
|
tabMapsButton.classList.toggle("text-gray-500", isSpj);
|
||||||
|
|
||||||
|
spjCardPanel.classList.toggle("hidden", !isSpj);
|
||||||
|
mapsCardPanel.classList.toggle("hidden", isSpj);
|
||||||
|
mapsCardPanel.classList.toggle("flex", !isSpj);
|
||||||
|
|
||||||
|
if (!isSpj) {
|
||||||
|
document.dispatchEvent(new CustomEvent("spj:show-maps"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tabSpjButton.addEventListener("click", function () {
|
||||||
|
activateTab("spj");
|
||||||
|
});
|
||||||
|
|
||||||
|
tabMapsButton.addEventListener("click", function () {
|
||||||
|
activateTab("maps");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const mapEl = document.getElementById("pickupMap");
|
||||||
|
const mapsCardPanel = document.getElementById("mapsCardPanel");
|
||||||
|
const recenterMapButton = document.getElementById("recenterMapButton");
|
||||||
|
if (!mapEl) return;
|
||||||
|
|
||||||
|
let mapInstance = null;
|
||||||
|
let mapBounds = null;
|
||||||
|
let mapInitialized = false;
|
||||||
|
|
||||||
|
function fitAllLocations() {
|
||||||
|
if (!mapInstance || !mapBounds) return;
|
||||||
|
|
||||||
|
mapInstance.invalidateSize();
|
||||||
|
|
||||||
|
if (Array.isArray(mapBounds) && mapBounds.length === 1) {
|
||||||
|
mapInstance.setView(mapBounds[0], 16);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mapInstance.fitBounds(mapBounds, {
|
||||||
|
padding: [24, 24],
|
||||||
|
maxZoom: 17
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureLeaflet() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (window.L && typeof window.L.map === "function") {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!document.getElementById("leaflet-css")) {
|
||||||
|
const css = document.createElement("link");
|
||||||
|
css.id = "leaflet-css";
|
||||||
|
css.rel = "stylesheet";
|
||||||
|
css.href = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.css";
|
||||||
|
document.head.appendChild(css);
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingScript = document.getElementById("leaflet-js");
|
||||||
|
if (existingScript) {
|
||||||
|
existingScript.addEventListener("load", resolve, { once: true });
|
||||||
|
existingScript.addEventListener("error", reject, { once: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.id = "leaflet-js";
|
||||||
|
script.src = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.js";
|
||||||
|
script.onload = resolve;
|
||||||
|
script.onerror = reject;
|
||||||
|
document.body.appendChild(script);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toNumber(value) {
|
||||||
|
const num = Number(value);
|
||||||
|
return Number.isFinite(num) ? num : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidLatLng(lat, lng) {
|
||||||
|
return lat !== null && lng !== null && Math.abs(lat) <= 90 && Math.abs(lng) <= 180;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLatLng(item) {
|
||||||
|
const lat = toNumber(item.latitude);
|
||||||
|
const lng = toNumber(item.longitude);
|
||||||
|
|
||||||
|
if (isValidLatLng(lat, lng)) return { lat, lng };
|
||||||
|
if (isValidLatLng(lng, lat)) return { lat: lng, lng: lat };
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function geocodeFromAddress(address) {
|
||||||
|
if (!address) return null;
|
||||||
|
try {
|
||||||
|
const res = await fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(address)}&limit=1`);
|
||||||
|
const data = await res.json();
|
||||||
|
if (!Array.isArray(data) || data.length === 0) return null;
|
||||||
|
const lat = toNumber(data[0].lat);
|
||||||
|
const lng = toNumber(data[0].lon);
|
||||||
|
return isValidLatLng(lat, lng) ? { lat, lng } : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getCoordinates(item) {
|
||||||
|
const normalized = normalizeLatLng(item);
|
||||||
|
if (normalized) return normalized;
|
||||||
|
return geocodeFromAddress(item.alamat);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getRoadRouteLatLngs(latLngs) {
|
||||||
|
if (!Array.isArray(latLngs) || latLngs.length < 2) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const coordString = latLngs
|
||||||
|
.map(([lat, lng]) => `${lng},${lat}`)
|
||||||
|
.join(";");
|
||||||
|
|
||||||
|
const url = `https://router.project-osrm.org/route/v1/driving/${coordString}?overview=full&geometries=geojson&steps=false&alternatives=false`;
|
||||||
|
const res = await fetch(url);
|
||||||
|
if (!res.ok) return null;
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
const coords = data?.routes?.[0]?.geometry?.coordinates;
|
||||||
|
if (!Array.isArray(coords) || coords.length < 2) return null;
|
||||||
|
|
||||||
|
return coords
|
||||||
|
.map(pair => [toNumber(pair?.[1]), toNumber(pair?.[0])])
|
||||||
|
.filter(([lat, lng]) => isValidLatLng(lat, lng));
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initMap() {
|
||||||
|
try {
|
||||||
|
if (mapInitialized && mapInstance) {
|
||||||
|
setTimeout(() => {
|
||||||
|
fitAllLocations();
|
||||||
|
}, 80);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ensureLeaflet();
|
||||||
|
const res = await fetch("@Url.Content("~/driver/json/pengangkutan.json")");
|
||||||
|
const payload = await res.json();
|
||||||
|
const rows = Array.isArray(payload?.data) ? payload.data : [];
|
||||||
|
|
||||||
|
const points = [];
|
||||||
|
for (const row of rows) {
|
||||||
|
const coords = await getCoordinates(row);
|
||||||
|
if (coords) points.push({ ...row, ...coords });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!points.length) {
|
||||||
|
mapEl.innerHTML = '<div class="h-full flex items-center justify-center text-xs font-semibold text-gray-500">Titik pengangkutan tidak ditemukan</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mapInstance = L.map("pickupMap", {
|
||||||
|
zoomControl: true,
|
||||||
|
attributionControl: true
|
||||||
|
});
|
||||||
|
|
||||||
|
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||||
|
maxZoom: 19,
|
||||||
|
attribution: "© OpenStreetMap"
|
||||||
|
}).addTo(mapInstance);
|
||||||
|
|
||||||
|
const pickupIcon = L.icon({
|
||||||
|
iconUrl: "@Url.Content("~/driver/images/loc2.svg")",
|
||||||
|
iconSize: [40, 40],
|
||||||
|
iconAnchor: [15, 30],
|
||||||
|
popupAnchor: [0, -28]
|
||||||
|
});
|
||||||
|
|
||||||
|
const latLngs = points.map(p => [p.lat, p.lng]);
|
||||||
|
|
||||||
|
points.forEach((point, index) => {
|
||||||
|
L.marker([point.lat, point.lng], { icon: pickupIcon })
|
||||||
|
.addTo(mapInstance)
|
||||||
|
.bindPopup(`<b>${index + 1}. ${point.name || "Lokasi"}</b><br>${point.alamat || "-"}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
let routeLatLngs = latLngs;
|
||||||
|
|
||||||
|
if (latLngs.length > 1) {
|
||||||
|
const routed = await getRoadRouteLatLngs(latLngs);
|
||||||
|
if (routed && routed.length > 1) {
|
||||||
|
routeLatLngs = routed;
|
||||||
|
}
|
||||||
|
|
||||||
|
L.polyline(routeLatLngs, {
|
||||||
|
color: "#0f2a3f",
|
||||||
|
weight: 4,
|
||||||
|
opacity: 0.9
|
||||||
|
}).addTo(mapInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
mapBounds = routeLatLngs;
|
||||||
|
fitAllLocations();
|
||||||
|
mapInitialized = true;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
fitAllLocations();
|
||||||
|
}, 80);
|
||||||
|
} catch (err) {
|
||||||
|
mapEl.innerHTML = '<div class="h-full flex items-center justify-center text-xs font-semibold text-red-500">Gagal memuat peta</div>';
|
||||||
|
console.error("Map init error:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("spj:show-maps", function () {
|
||||||
|
initMap();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (recenterMapButton) {
|
||||||
|
recenterMapButton.addEventListener("click", async function () {
|
||||||
|
if (!mapInitialized || !mapInstance) {
|
||||||
|
await initMap();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fitAllLocations();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mapsCardPanel && !mapsCardPanel.classList.contains("hidden")) {
|
||||||
|
initMap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const btn = document.getElementById("profileMenuButton");
|
||||||
|
const dropdown = document.getElementById("profileMenuDropdown");
|
||||||
|
|
||||||
|
btn.addEventListener("click", function (e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
dropdown.classList.toggle("hidden");
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("click", function () {
|
||||||
|
dropdown.classList.add("hidden");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const qrTrigger = document.getElementById("qrCodeTrigger");
|
||||||
|
const qrModal = document.getElementById("qrCodeModal");
|
||||||
|
const closeModal = document.getElementById("closeQrModal");
|
||||||
|
let originalBrightness = null;
|
||||||
|
let wakeLock = null;
|
||||||
|
|
||||||
|
async function setMaxBrightness() {
|
||||||
|
try {
|
||||||
|
if ('wakeLock' in navigator) {
|
||||||
|
wakeLock = await navigator.wakeLock.request('screen');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('screen' in navigator && 'brightness' in navigator.screen) {
|
||||||
|
originalBrightness = navigator.screen.brightness;
|
||||||
|
navigator.screen.brightness = 1.0;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Brightness control not supported:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function restoreOriginalBrightness() {
|
||||||
|
try {
|
||||||
|
if (wakeLock) {
|
||||||
|
await wakeLock.release();
|
||||||
|
wakeLock = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('screen' in navigator && 'brightness' in navigator.screen && originalBrightness !== null) {
|
||||||
|
navigator.screen.brightness = originalBrightness;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Error restoring brightness:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qrTrigger.addEventListener("click", function () {
|
||||||
|
qrModal.classList.remove("hidden");
|
||||||
|
qrModal.classList.add("flex");
|
||||||
|
setMaxBrightness();
|
||||||
|
|
||||||
|
if ('vibrate' in navigator) {
|
||||||
|
navigator.vibrate(50);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function closeQrCodeModal() {
|
||||||
|
qrModal.classList.add("hidden");
|
||||||
|
qrModal.classList.remove("flex");
|
||||||
|
restoreOriginalBrightness();
|
||||||
|
}
|
||||||
|
|
||||||
|
closeModal.addEventListener("click", closeQrCodeModal);
|
||||||
|
|
||||||
|
qrModal.addEventListener("click", function (e) {
|
||||||
|
if (e.target === qrModal) {
|
||||||
|
closeQrCodeModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("keydown", function (e) {
|
||||||
|
if (e.key === "Escape" && !qrModal.classList.contains("hidden")) {
|
||||||
|
closeQrCodeModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("visibilitychange", function () {
|
||||||
|
if (document.hidden && !qrModal.classList.contains("hidden")) {
|
||||||
|
restoreOriginalBrightness();
|
||||||
|
} else if (!document.hidden && !qrModal.classList.contains("hidden")) {
|
||||||
|
setMaxBrightness();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const addModal = document.getElementById("addPickupModal");
|
||||||
|
const closeAddModal = document.getElementById("closeAddModal");
|
||||||
|
const pickupSelect = document.getElementById("pickupSelect");
|
||||||
|
const plusButton = document.getElementById("addPickupButton");
|
||||||
|
|
||||||
|
function ensureSelect2() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (window.jQuery && window.jQuery.fn.select2) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!document.getElementById("select2-css")) {
|
||||||
|
const css = document.createElement("link");
|
||||||
|
css.id = "select2-css";
|
||||||
|
css.rel = "stylesheet";
|
||||||
|
css.href = "https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css";
|
||||||
|
document.head.appendChild(css);
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingScript = document.getElementById("select2-js");
|
||||||
|
if (existingScript) {
|
||||||
|
existingScript.addEventListener("load", resolve, { once: true });
|
||||||
|
existingScript.addEventListener("error", reject, { once: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.id = "select2-js";
|
||||||
|
script.src = "https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js";
|
||||||
|
script.onload = resolve;
|
||||||
|
script.onerror = reject;
|
||||||
|
document.body.appendChild(script);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateDistance(lat1, lon1, lat2, lon2) {
|
||||||
|
const R = 6371e3;
|
||||||
|
const φ1 = lat1 * Math.PI / 180;
|
||||||
|
const φ2 = lat2 * Math.PI / 180;
|
||||||
|
const Δφ = (lat2 - lat1) * Math.PI / 180;
|
||||||
|
const Δλ = (lon2 - lon1) * Math.PI / 180;
|
||||||
|
|
||||||
|
const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
|
||||||
|
Math.cos(φ1) * Math.cos(φ2) *
|
||||||
|
Math.sin(Δλ/2) * Math.sin(Δλ/2);
|
||||||
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
|
||||||
|
|
||||||
|
return Math.round(R * c);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentLocation() {
|
||||||
|
const lat = parseFloat(localStorage.getItem("user_latitude"));
|
||||||
|
const lng = parseFloat(localStorage.getItem("user_longitude"));
|
||||||
|
return { lat, lng };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initAddModal() {
|
||||||
|
try {
|
||||||
|
await ensureSelect2();
|
||||||
|
|
||||||
|
const res = await fetch("@Url.Content("~/driver/json/pickup_locations.json")");
|
||||||
|
const payload = await res.json();
|
||||||
|
const locations = Array.isArray(payload?.data) ? payload.data : [];
|
||||||
|
|
||||||
|
const current = getCurrentLocation();
|
||||||
|
const data = locations.map((loc, index) => {
|
||||||
|
let distanceText = '';
|
||||||
|
if (current.lat && current.lng && loc.latitude && loc.longitude) {
|
||||||
|
const distance = calculateDistance(current.lat, current.lng, loc.latitude, loc.longitude);
|
||||||
|
distanceText = ` (${distance} m)`;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: index,
|
||||||
|
text: `${loc.name} - ${loc.alamat}${distanceText}`,
|
||||||
|
lat: loc.latitude,
|
||||||
|
lng: loc.longitude,
|
||||||
|
name: loc.name,
|
||||||
|
alamat: loc.alamat,
|
||||||
|
distance: distanceText
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$(pickupSelect).select2({
|
||||||
|
data: data,
|
||||||
|
placeholder: "Cari lokasi pengangkutan...",
|
||||||
|
allowClear: true,
|
||||||
|
width: '100%',
|
||||||
|
templateResult: function (item) {
|
||||||
|
if (!item.id) return item.text;
|
||||||
|
return $(`<div><strong>${item.name}</strong><br><small>${item.alamat}${item.distance}</small></div>`);
|
||||||
|
},
|
||||||
|
templateSelection: function (item) {
|
||||||
|
if (!item.id) return item.text;
|
||||||
|
return `${item.name} - ${item.alamat}${item.distance}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(pickupSelect).on('select2:select', function (e) {
|
||||||
|
});
|
||||||
|
|
||||||
|
$(pickupSelect).on('select2:clear', function () {
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error initializing add modal:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plusButton.addEventListener("click", function () {
|
||||||
|
addModal.classList.remove("hidden");
|
||||||
|
addModal.classList.add("flex");
|
||||||
|
initAddModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
function closeAddPickupModal() {
|
||||||
|
addModal.classList.add("hidden");
|
||||||
|
addModal.classList.remove("flex");
|
||||||
|
$(pickupSelect).val(null).trigger('change');
|
||||||
|
}
|
||||||
|
|
||||||
|
closeAddModal.addEventListener("click", closeAddPickupModal);
|
||||||
|
|
||||||
|
addModal.addEventListener("click", function (e) {
|
||||||
|
if (e.target === addModal) {
|
||||||
|
closeAddPickupModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("keydown", function (e) {
|
||||||
|
if (e.key === "Escape" && !addModal.classList.contains("hidden")) {
|
||||||
|
closeAddPickupModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function updateDateTime() {
|
||||||
|
const now = new Date();
|
||||||
|
const options = {
|
||||||
|
weekday: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
hour12: false
|
||||||
|
};
|
||||||
|
const formatted = now.toLocaleDateString('id-ID', options);
|
||||||
|
document.getElementById('currentDateTime').textContent = formatted;
|
||||||
|
}
|
||||||
|
updateDateTime();
|
||||||
|
setInterval(updateDateTime, 1000);
|
||||||
|
</script>
|
||||||
|
</register-block>
|
||||||
|
|
@ -0,0 +1,221 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Admin/Transport/SpjDriverUpst/Shared/_Layout.cshtml";
|
||||||
|
ViewData["Title"] = "Home Page";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="container max-w-sm mx-auto bg-white min-h-screen">
|
||||||
|
|
||||||
|
<div class="absolute top-0 max-w-sm container mx-auto bg-orange-500 text-white rounded-br-[125px] h-[250px] flex flex-row justify-between items-start px-6 py-6 shadow-lg z-20">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<h1 class="text-md font-bold leading-tight text-white">Bonny Agung Putra</h1>
|
||||||
|
<p class="text-xs opacity-90 font-medium text-orange-100">Driver UPST</span></p>
|
||||||
|
<div class="mt-5 flex items-center gap-2">
|
||||||
|
<i class="w-4 h-4 text-white" data-lucide="map-pin"></i>
|
||||||
|
<span class="text-sm opacity-90">Lokasi Anda:</span>
|
||||||
|
</div>
|
||||||
|
<p id="userLocation" class="font-semibold text-xs tracking-wide cursor-pointer underline text-white hover:text-orange-200 transition">
|
||||||
|
Mendeteksi lokasi...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<div class="relative flex flex-col items-center justify-center">
|
||||||
|
<div class="w-12 h-12 rounded-full border-3 border-white overflow-hidden shadow-md flex items-center justify-center cursor-pointer group" id="profileMenuButton">
|
||||||
|
<i class="w-8 h-8 text-white" data-lucide="user"></i>
|
||||||
|
</div>
|
||||||
|
<div id="profileMenuDropdown" class="absolute top-12 right-0 mt-2 w-32 bg-white rounded shadow-lg py-2 z-50 hidden">
|
||||||
|
<form method="post" asp-controller="Auth" asp-action="Logout">
|
||||||
|
<button type="submit" class="hover:cursor-pointer flex items-center gap-2 w-full text-left px-4 py-2 text-sm font-semibold text-red-600 hover:bg-red-50 transition rounded-md">
|
||||||
|
<i class="w-4 h-4" data-lucide="log-out"></i>
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-100 mx-4 px-6 py-12 mt-40 rounded-3xl relative overflow-hidden shadow-2xl z-21 border border-slate-200">
|
||||||
|
<div class="absolute top-0 left-0 w-full h-full pointer-events-none">
|
||||||
|
<div class="absolute top-4 right-8 w-6 h-6 bg-orange-300 rounded-full opacity-60 animate-bounce" style="animation-delay: 0.5s;"></div>
|
||||||
|
<div class="absolute top-12 left-12 w-4 h-4 bg-blue-400 rounded-full opacity-40 animate-pulse" style="animation-delay: 1s;"></div>
|
||||||
|
<div class="absolute bottom-8 left-8 w-5 h-5 bg-indigo-300 rounded-full opacity-50 animate-bounce" style="animation-delay: 1.5s;"></div>
|
||||||
|
<div class="absolute bottom-16 right-16 w-3 h-3 bg-slate-400 rounded-full opacity-30 animate-pulse" style="animation-delay: 2s;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="relative z-10 text-center">
|
||||||
|
<div class="w-24 h-24 mx-auto mb-6 relative">
|
||||||
|
<div class="w-full h-full bg-gradient-to-br from-orange-400 to-orange-600 rounded-full flex items-center justify-center shadow-xl animate-pulse">
|
||||||
|
<div class="w-20 h-20 bg-white rounded-full flex items-center justify-center">
|
||||||
|
<i class="w-10 h-10 text-orange-500" data-lucide="clipboard-list"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute inset-0 border-4 border-orange-300 rounded-full animate-ping opacity-30"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<h2 class="text-xl font-bold bg-gradient-to-r from-slate-700 to-slate-900 bg-clip-text text-transparent">
|
||||||
|
Belum Ada SPJ
|
||||||
|
</h2>
|
||||||
|
<p class="text-sm text-slate-600 leading-relaxed px-4 mb-2">
|
||||||
|
Anda belum memiliki <span class="font-semibold text-orange-600">Surat Perintah Jalan</span> yang aktif saat ini.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white/70 backdrop-blur-sm border border-white/30 rounded-2xl p-5 mb-6 text-left">
|
||||||
|
<div class="space-y-2 text-xs text-slate-600">
|
||||||
|
<div class="flex items-start gap-2">
|
||||||
|
<div class="w-1 h-1 bg-orange-400 rounded-full mt-1.5 flex-shrink-0"></div>
|
||||||
|
<p>SPJ akan diterbitkan oleh admin sesuai jadwal kerja</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-start gap-2">
|
||||||
|
<div class="w-1 h-1 bg-orange-400 rounded-full mt-1.5 flex-shrink-0"></div>
|
||||||
|
<p>Periksa koneksi internet dan aktifkan lokasi GPS</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="refreshButton" class="bg-gradient-to-r from-orange-500 to-orange-600 hover:from-orange-600 hover:to-orange-700 text-white px-6 py-3 rounded-2xl text-sm font-bold shadow-lg hover:shadow-xl transition-all duration-300 flex items-center gap-2 mx-auto transform hover:scale-105">
|
||||||
|
<i class="w-4 h-4" data-lucide="refresh-cw" id="refreshIcon"></i>
|
||||||
|
<span id="refreshText">Refresh Halaman</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<partial name="~/Views/Admin/Transport/SpjDriverUpst/Shared/Components/_Navigation.cshtml" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<register-block dynamic-section="scripts" key="jsHomeKosong">
|
||||||
|
<style>
|
||||||
|
@@keyframes float {
|
||||||
|
0%, 100% { transform: translateY(0px); }
|
||||||
|
50% { transform: translateY(-10px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes shimmer {
|
||||||
|
0% { background-position: -200px 0; }
|
||||||
|
100% { background-position: calc(200px + 100%) 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.float-animation {
|
||||||
|
animation: float 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shimmer {
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
|
||||||
|
background-size: 200px 100%;
|
||||||
|
animation: shimmer 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-spin {
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes spin {
|
||||||
|
from { transform: rotate(0deg); }
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const userLocationEl = document.getElementById("userLocation");
|
||||||
|
|
||||||
|
function reverseGeocode(lat, lng) {
|
||||||
|
fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
const address = data.display_name || `${lat}, ${lng}`;
|
||||||
|
userLocationEl.textContent = address;
|
||||||
|
localStorage.setItem("user_latitude", lat);
|
||||||
|
localStorage.setItem("user_longitude", lng);
|
||||||
|
localStorage.setItem("user_address", address);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
userLocationEl.textContent = `${lat}, ${lng}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocationUpdate() {
|
||||||
|
if ("geolocation" in navigator) {
|
||||||
|
userLocationEl.textContent = "Mendeteksi lokasi baru...";
|
||||||
|
navigator.geolocation.getCurrentPosition(
|
||||||
|
function (position) {
|
||||||
|
const lat = position.coords.latitude.toFixed(6);
|
||||||
|
const lng = position.coords.longitude.toFixed(6);
|
||||||
|
reverseGeocode(lat, lng);
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
userLocationEl.textContent = "Lokasi tidak diizinkan";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
userLocationEl.textContent = "Browser tidak mendukung lokasi";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const savedAddress = localStorage.getItem("user_address");
|
||||||
|
if (savedAddress) {
|
||||||
|
userLocationEl.textContent = savedAddress;
|
||||||
|
} else {
|
||||||
|
getLocationUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
userLocationEl.addEventListener("click", function () {
|
||||||
|
getLocationUpdate();
|
||||||
|
});
|
||||||
|
|
||||||
|
const refreshBtn = document.getElementById('refreshButton');
|
||||||
|
const refreshIcon = document.getElementById('refreshIcon');
|
||||||
|
const refreshText = document.getElementById('refreshText');
|
||||||
|
|
||||||
|
if (refreshBtn) {
|
||||||
|
refreshBtn.addEventListener("click", function() {
|
||||||
|
refreshIcon.style.animation = 'spin 1s linear infinite';
|
||||||
|
refreshText.textContent = 'Memuat...';
|
||||||
|
refreshBtn.disabled = true;
|
||||||
|
refreshBtn.style.opacity = '0.8';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
location.reload();
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainIcon = document.querySelector('[data-lucide="clipboard-list"]');
|
||||||
|
if (mainIcon) {
|
||||||
|
mainIcon.closest('.w-24').classList.add('float-animation');
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainCard = document.querySelector('.bg-gradient-to-br');
|
||||||
|
if (mainCard) {
|
||||||
|
mainCard.style.opacity = '0';
|
||||||
|
mainCard.style.transform = 'translateY(20px)';
|
||||||
|
mainCard.style.transition = 'all 0.6s ease-out';
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
mainCard.style.opacity = '1';
|
||||||
|
mainCard.style.transform = 'translateY(0)';
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const btn = document.getElementById("profileMenuButton");
|
||||||
|
const dropdown = document.getElementById("profileMenuDropdown");
|
||||||
|
|
||||||
|
btn.addEventListener("click", function (e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
dropdown.classList.toggle("hidden");
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("click", function () {
|
||||||
|
dropdown.classList.add("hidden");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</register-block>
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Privacy Policy";
|
||||||
|
}
|
||||||
|
<h1>@ViewData["Title"]</h1>
|
||||||
|
|
||||||
|
<p>Use this page to detail your site's privacy policy.</p>
|
||||||
|
|
@ -0,0 +1,880 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Admin/Transport/SpjDriverUpst/Shared/_Layout.cshtml";
|
||||||
|
ViewData["Title"] = "Login eSPJ";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="bg-gradient-to-br from-indigo-50 via-white to-purple-50">
|
||||||
|
<div class="max-w-sm mx-auto bg-white min-h-screen shadow-xl relative overflow-hidden">
|
||||||
|
|
||||||
|
<!-- Splash Screens Container -->
|
||||||
|
<div id="splashContainer" class="splash-container">
|
||||||
|
|
||||||
|
<!-- Welcome Screen -->
|
||||||
|
<div class="slide">
|
||||||
|
<div class="slide-content flex flex-col items-center">
|
||||||
|
@* <div class="icon-circle">
|
||||||
|
<svg width="32" height="32" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M13 10V3L4 14h7v7l9-11h-7z" color="white"></path>
|
||||||
|
</svg>
|
||||||
|
</div> *@
|
||||||
|
<img class="w-20 h-20" src="@Url.Content("~/driver/logo.svg")" alt="">
|
||||||
|
<h2>Selamat Datang di eSPJ</h2>
|
||||||
|
<p>Aplikasi modern untuk pengelolaan Surat Perintah Jalan Driver yang efisien dan terintegrasi.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Monitoring Screen -->
|
||||||
|
<div class="slide">
|
||||||
|
<div class="slide-content">
|
||||||
|
<div class="icon-circle">
|
||||||
|
<i class="w-10 h-10 text-white" data-lucide="home"></i>
|
||||||
|
</div>
|
||||||
|
<h2>Monitoring Real-Time</h2>
|
||||||
|
<p>Pantau status SPJ driver, kondisi kendaraan, dan muatan di setiap lokasi secara langsung.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Integrasi Lengkap Screen -->
|
||||||
|
<div class="slide">
|
||||||
|
<div class="slide-content">
|
||||||
|
<div class="icon-circle">
|
||||||
|
<svg width="32" height="32" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M12 4v16m8-8H4" color="white"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2>Integrasi Lengkap</h2>
|
||||||
|
<p>Sistem terhubung antara admin, driver, dan manajemen untuk pengelolaan SPJ yang efisien.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Login Screen -->
|
||||||
|
<div class="slide">
|
||||||
|
<div style="width: 100%; display: flex; align-items: center; justify-content: center; min-height: 100vh;">
|
||||||
|
<div class="login-form">
|
||||||
|
<div class="login-icon">
|
||||||
|
<svg width="24" height="24" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" color="white"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="text-align: center; margin-bottom: 2rem;">
|
||||||
|
<h2 style="font-size: 1.5rem; font-weight: 700; color: #1f2937; margin-bottom: 0.5rem;">Masuk ke eSPJ</h2>
|
||||||
|
<p style="color: #6b7280; font-size: 0.9rem;">Gunakan Single Sign-On untuk akses yang aman</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="sso-btn" onclick="showSSOModal()">
|
||||||
|
<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"></path>
|
||||||
|
</svg>
|
||||||
|
Masuk dengan SSO
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigation Controls -->
|
||||||
|
<div id="navigationControls" class="navigation">
|
||||||
|
<!-- Dot Navigation -->
|
||||||
|
<div class="dots">
|
||||||
|
<div class="dot active" data-slide="0"></div>
|
||||||
|
<div class="dot" data-slide="1"></div>
|
||||||
|
<div class="dot" data-slide="2"></div>
|
||||||
|
<div class="dot" data-slide="3"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigation Buttons -->
|
||||||
|
<div class="nav-buttons">
|
||||||
|
<button id="skipBtn" class="btn btn-skip">Lewati</button>
|
||||||
|
<button id="nextBtn" class="btn btn-primary">Selanjutnya</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SSO Webview Modal -->
|
||||||
|
<div id="ssoModal" class="webview-modal">
|
||||||
|
<div class="webview-container">
|
||||||
|
<div class="webview-header">
|
||||||
|
<div class="webview-title">Single Sign-On</div>
|
||||||
|
<button class="close-btn" onclick="closeSSOModal()">
|
||||||
|
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div style="position: relative; height: calc(100% - 60px);">
|
||||||
|
<div id="loadingSpinner" class="loading-spinner"></div>
|
||||||
|
<iframe id="ssoIframe" class="webview-iframe" src="" style="display: none;"></iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Update Available Notification -->
|
||||||
|
<div id="updateNotification" class="update-notification" style="display: none;">
|
||||||
|
<div class="update-content">
|
||||||
|
<div class="update-icon">
|
||||||
|
<svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="update-text">
|
||||||
|
<p>Update tersedia</p>
|
||||||
|
<small>Versi baru aplikasi sudah tersedia</small>
|
||||||
|
</div>
|
||||||
|
<button onclick="applyUpdate()" class="update-btn">Update</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* Display any server-side validation errors *@
|
||||||
|
@if (ViewData.ModelState.ErrorCount > 0)
|
||||||
|
{
|
||||||
|
<div id="errorNotification" class="error-notification">
|
||||||
|
<div class="error-content">
|
||||||
|
@foreach (var error in ViewData.ModelState.Values.SelectMany(v => v.Errors))
|
||||||
|
{
|
||||||
|
<p>@error.ErrorMessage</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@section Styles {
|
||||||
|
<style>
|
||||||
|
|
||||||
|
.splash-container {
|
||||||
|
width: 400%;
|
||||||
|
display: flex;
|
||||||
|
transition: transform 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide {
|
||||||
|
width: 25%;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 2rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-content {
|
||||||
|
text-align: center;
|
||||||
|
animation: slideIn 0.8s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-circle {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(135deg, #fb923c, #f59e42);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0 auto 2rem;
|
||||||
|
box-shadow: 0 10px 30px rgba(251, 146, 60, 0.3);
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes pulse {
|
||||||
|
0%, 100% { transform: scale(1); }
|
||||||
|
50% { transform: scale(1.05); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide h2 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1f2937;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide p {
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navigation {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 2rem;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 0 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dots {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #e5e7eb;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot.active {
|
||||||
|
background: linear-gradient(135deg, #fb923c, #f59e42);
|
||||||
|
transform: scale(1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border: none;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-skip {
|
||||||
|
background: transparent;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-skip:hover {
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: linear-gradient(135deg, #fb923c, #f59e42);
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 4px 15px rgba(251, 146, 60, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
box-shadow: 0 8px 25px rgba(251, 146, 60, 0.4);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-radius: 24px;
|
||||||
|
padding: 2rem;
|
||||||
|
margin: 2rem;
|
||||||
|
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
||||||
|
animation: fadeInUp 0.8s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(40px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-icon {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: linear-gradient(135deg, #fb923c, #f59e42);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0 auto 1.5rem;
|
||||||
|
box-shadow: 0 8px 20px rgba(251, 146, 60, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sso-btn, .w-full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sso-btn {
|
||||||
|
padding: 1rem;
|
||||||
|
background: linear-gradient(135deg, #fb923c, #f59e42);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 15px rgba(251, 146, 60, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sso-btn:hover {
|
||||||
|
box-shadow: 0 8px 25px rgba(251, 146, 60, 0.4);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.webview-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
z-index: 1000;
|
||||||
|
display: none;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.webview-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 90vw;
|
||||||
|
max-width: 400px;
|
||||||
|
height: 80vh;
|
||||||
|
max-height: 600px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3);
|
||||||
|
animation: modalSlideIn 0.4s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes modalSlideIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate(-50%, -60%);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.webview-header {
|
||||||
|
background: linear-gradient(135deg, #fb923c, #f59e42);
|
||||||
|
color: white;
|
||||||
|
padding: 1rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webview-title {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.webview-iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 60px);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 3px solid #f3f4f6;
|
||||||
|
border-top: 3px solid #fb923c;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes spin {
|
||||||
|
0% { transform: translate(-50%, -50%) rotate(0deg); }
|
||||||
|
100% { transform: translate(-50%, -50%) rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-notification {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||||
|
padding: 1rem;
|
||||||
|
z-index: 1001;
|
||||||
|
animation: slideDown 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes slideDown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-50%) translateY(-20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(-50%) translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-icon {
|
||||||
|
color: #fb923c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-btn {
|
||||||
|
background: linear-gradient(135deg, #fb923c, #f59e42);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-notification {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
background: #fee2e2;
|
||||||
|
border: 1px solid #fca5a5;
|
||||||
|
color: #dc2626;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
z-index: 1001;
|
||||||
|
animation: slideInRight 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes slideInRight {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PWA Status Bar Safe Area */
|
||||||
|
@@supports (padding-top: env(safe-area-inset-top)) {
|
||||||
|
.max-w-sm {
|
||||||
|
padding-top: env(safe-area-inset-top);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@@media (max-width: 480px) {
|
||||||
|
.slide {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
margin: 1rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webview-container {
|
||||||
|
width: 95vw;
|
||||||
|
height: 85vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
}
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<script>
|
||||||
|
// PWA Service Worker Registration
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
navigator.serviceWorker.register('/driver/sw.js')
|
||||||
|
.then((registration) => {
|
||||||
|
console.log('SW registered: ', registration);
|
||||||
|
|
||||||
|
// Check for updates
|
||||||
|
registration.addEventListener('updatefound', () => {
|
||||||
|
const newWorker = registration.installing;
|
||||||
|
newWorker.addEventListener('statechange', () => {
|
||||||
|
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
|
||||||
|
showUpdateNotification();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((registrationError) => {
|
||||||
|
console.log('SW registration failed: ', registrationError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Splash Screen Navigation
|
||||||
|
let currentSlide = 0;
|
||||||
|
const totalSlides = 4;
|
||||||
|
const container = document.getElementById('splashContainer');
|
||||||
|
const dots = document.querySelectorAll('.dot');
|
||||||
|
const nextBtn = document.getElementById('nextBtn');
|
||||||
|
const skipBtn = document.getElementById('skipBtn');
|
||||||
|
const navigationControls = document.getElementById('navigationControls');
|
||||||
|
|
||||||
|
function updateSlide(slideIndex) {
|
||||||
|
// Ensure slideIndex is within bounds
|
||||||
|
if (slideIndex < 0) slideIndex = 0;
|
||||||
|
if (slideIndex >= totalSlides) slideIndex = totalSlides - 1;
|
||||||
|
|
||||||
|
currentSlide = slideIndex;
|
||||||
|
container.style.transform = `translateX(-${slideIndex * 25}%)`;
|
||||||
|
|
||||||
|
// Update dots
|
||||||
|
dots.forEach((dot, index) => {
|
||||||
|
dot.classList.toggle('active', index === slideIndex);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update navigation visibility and button text
|
||||||
|
if (slideIndex === totalSlides - 1) {
|
||||||
|
navigationControls.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
navigationControls.style.display = 'block';
|
||||||
|
nextBtn.textContent = slideIndex === totalSlides - 2 ? 'Masuk' : 'Selanjutnya';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextSlide() {
|
||||||
|
if (currentSlide < totalSlides - 1) {
|
||||||
|
currentSlide++;
|
||||||
|
updateSlide(currentSlide);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function skipToLogin() {
|
||||||
|
currentSlide = totalSlides - 1;
|
||||||
|
updateSlide(currentSlide);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event Listeners
|
||||||
|
nextBtn.addEventListener('click', nextSlide);
|
||||||
|
skipBtn.addEventListener('click', skipToLogin);
|
||||||
|
|
||||||
|
dots.forEach((dot, index) => {
|
||||||
|
dot.addEventListener('click', () => {
|
||||||
|
currentSlide = index;
|
||||||
|
updateSlide(currentSlide);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Touch/Swipe Navigation
|
||||||
|
let startX = 0;
|
||||||
|
let endX = 0;
|
||||||
|
let isTouch = false;
|
||||||
|
|
||||||
|
container.addEventListener('touchstart', (e) => {
|
||||||
|
startX = e.touches[0].clientX;
|
||||||
|
isTouch = true;
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
container.addEventListener('touchmove', (e) => {
|
||||||
|
if (!isTouch) return;
|
||||||
|
// Prevent default scrolling behavior during swipe
|
||||||
|
e.preventDefault();
|
||||||
|
}, { passive: false });
|
||||||
|
|
||||||
|
container.addEventListener('touchend', (e) => {
|
||||||
|
if (!isTouch) return;
|
||||||
|
endX = e.changedTouches[0].clientX;
|
||||||
|
handleSwipe();
|
||||||
|
isTouch = false;
|
||||||
|
}, { passive: true });
|
||||||
|
|
||||||
|
function handleSwipe() {
|
||||||
|
const swipeThreshold = 50;
|
||||||
|
const diff = startX - endX;
|
||||||
|
|
||||||
|
if (Math.abs(diff) > swipeThreshold) {
|
||||||
|
if (diff > 0 && currentSlide < totalSlides - 1) {
|
||||||
|
// Swipe left - next slide
|
||||||
|
nextSlide();
|
||||||
|
} else if (diff < 0 && currentSlide > 0) {
|
||||||
|
// Swipe right - previous slide
|
||||||
|
currentSlide--;
|
||||||
|
updateSlide(currentSlide);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSO Modal Functions
|
||||||
|
function showSSOModal() {
|
||||||
|
const modal = document.getElementById('ssoModal');
|
||||||
|
const iframe = document.getElementById('ssoIframe');
|
||||||
|
const spinner = document.getElementById('loadingSpinner');
|
||||||
|
|
||||||
|
// Show modal
|
||||||
|
modal.style.display = 'block';
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
||||||
|
// Show loading spinner
|
||||||
|
spinner.style.display = 'block';
|
||||||
|
iframe.style.display = 'none';
|
||||||
|
|
||||||
|
// Set SSO URL from server-side ViewBag
|
||||||
|
const ssoUrl = '@Html.Raw(ViewBag.SSOLoginUrl ?? "")';
|
||||||
|
if (ssoUrl) {
|
||||||
|
iframe.src = ssoUrl;
|
||||||
|
} else {
|
||||||
|
console.error('SSO URL not provided');
|
||||||
|
closeSSOModal();
|
||||||
|
alert('SSO tidak tersedia saat ini. Silakan gunakan login manual.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle iframe load
|
||||||
|
iframe.onload = function() {
|
||||||
|
spinner.style.display = 'none';
|
||||||
|
iframe.style.display = 'block';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle iframe error
|
||||||
|
iframe.onerror = function() {
|
||||||
|
spinner.style.display = 'none';
|
||||||
|
alert('Gagal memuat halaman SSO. Silakan coba lagi.');
|
||||||
|
closeSSOModal();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeSSOModal() {
|
||||||
|
const modal = document.getElementById('ssoModal');
|
||||||
|
const iframe = document.getElementById('ssoIframe');
|
||||||
|
|
||||||
|
modal.style.display = 'none';
|
||||||
|
document.body.style.overflow = 'auto';
|
||||||
|
iframe.src = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for SSO success message from iframe
|
||||||
|
window.addEventListener('message', function(event) {
|
||||||
|
// Verify origin for security (uncomment and set your SSO domain)
|
||||||
|
// const allowedOrigins = ['https://your-sso-domain.com'];
|
||||||
|
// if (!allowedOrigins.includes(event.origin)) return;
|
||||||
|
|
||||||
|
if (event.data === 'sso-success' || (event.data && event.data.type === 'sso-success')) {
|
||||||
|
closeSSOModal();
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
showNotification('Login berhasil! Mengarahkan...', 'success');
|
||||||
|
|
||||||
|
// Store login state
|
||||||
|
if (typeof(Storage) !== "undefined") {
|
||||||
|
sessionStorage.setItem('isLoggedIn', 'true');
|
||||||
|
sessionStorage.setItem('loginTime', new Date().toISOString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect after short delay
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = '@Url.Action("Index", "Home")' || '/';
|
||||||
|
}, 1500);
|
||||||
|
} else if (event.data === 'sso-error' || (event.data && event.data.type === 'sso-error')) {
|
||||||
|
closeSSOModal();
|
||||||
|
showNotification('Login gagal. Silakan coba lagi.', 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle back button on Android PWA
|
||||||
|
window.addEventListener('popstate', function(event) {
|
||||||
|
const modal = document.getElementById('ssoModal');
|
||||||
|
if (modal.style.display === 'block') {
|
||||||
|
closeSSOModal();
|
||||||
|
event.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// PWA Update Functions
|
||||||
|
function showUpdateNotification() {
|
||||||
|
const notification = document.getElementById('updateNotification');
|
||||||
|
if (notification) {
|
||||||
|
notification.style.display = 'block';
|
||||||
|
|
||||||
|
// Auto-hide after 10 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
notification.style.display = 'none';
|
||||||
|
}, 10000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyUpdate() {
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.getRegistration().then((registration) => {
|
||||||
|
if (registration && registration.waiting) {
|
||||||
|
registration.waiting.postMessage({ type: 'SKIP_WAITING' });
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notification System
|
||||||
|
function showNotification(message, type = 'info') {
|
||||||
|
const notification = document.createElement('div');
|
||||||
|
notification.className = `notification notification-${type}`;
|
||||||
|
notification.innerHTML = `
|
||||||
|
<div class="notification-content">
|
||||||
|
<span>${message}</span>
|
||||||
|
<button onclick="this.parentElement.parentElement.remove()">×</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Add notification styles
|
||||||
|
notification.style.cssText = `
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
background: ${type === 'success' ? '#dcfce7' : type === 'error' ? '#fee2e2' : '#f3f4f6'};
|
||||||
|
color: ${type === 'success' ? '#166534' : type === 'error' ? '#dc2626' : '#374151'};
|
||||||
|
border: 1px solid ${type === 'success' ? '#bbf7d0' : type === 'error' ? '#fca5a5' : '#d1d5db'};
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
z-index: 1002;
|
||||||
|
animation: slideInRight 0.3s ease-out;
|
||||||
|
max-width: 300px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(notification);
|
||||||
|
|
||||||
|
// Auto-remove after 5 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
if (notification.parentNode) {
|
||||||
|
notification.remove();
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle connection status
|
||||||
|
function updateConnectionStatus() {
|
||||||
|
const isOnline = navigator.onLine;
|
||||||
|
|
||||||
|
if (!isOnline) {
|
||||||
|
showNotification('Koneksi internet terputus. Beberapa fitur mungkin tidak tersedia.', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('online', () => {
|
||||||
|
showNotification('Koneksi internet kembali terhubung.', 'success');
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('offline', updateConnectionStatus);
|
||||||
|
|
||||||
|
// Initialize everything when DOM is loaded
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
console.log('DOM Content Loaded');
|
||||||
|
|
||||||
|
// Initialize Lucide icons
|
||||||
|
if (typeof lucide !== 'undefined') {
|
||||||
|
lucide.createIcons();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-hide error notifications after page load
|
||||||
|
const errorNotification = document.getElementById('errorNotification');
|
||||||
|
if (errorNotification) {
|
||||||
|
setTimeout(() => {
|
||||||
|
errorNotification.style.display = 'none';
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize splash screen - ALWAYS start from first slide
|
||||||
|
currentSlide = 0;
|
||||||
|
updateSlide(0);
|
||||||
|
|
||||||
|
console.log('Splash screen initialized at slide:', currentSlide);
|
||||||
|
|
||||||
|
// Optional: Check if user is a returning visitor and show a different experience
|
||||||
|
// But don't automatically skip the splash screen
|
||||||
|
const isReturningUser = sessionStorage.getItem('hasSeenSplash') === 'true';
|
||||||
|
if (isReturningUser) {
|
||||||
|
// You can add a "Skip Intro" button or similar UX improvement
|
||||||
|
console.log('Returning user detected');
|
||||||
|
// But still show the splash screen by default
|
||||||
|
} else {
|
||||||
|
sessionStorage.setItem('hasSeenSplash', 'true');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// PWA Install Prompt
|
||||||
|
let deferredPrompt;
|
||||||
|
|
||||||
|
window.addEventListener('beforeinstallprompt', (e) => {
|
||||||
|
// Prevent Chrome 67 and earlier from automatically showing the prompt
|
||||||
|
e.preventDefault();
|
||||||
|
// Stash the event so it can be triggered later
|
||||||
|
deferredPrompt = e;
|
||||||
|
|
||||||
|
// Show install button/notification
|
||||||
|
showInstallPrompt();
|
||||||
|
});
|
||||||
|
|
||||||
|
function showInstallPrompt() {
|
||||||
|
const installNotification = document.createElement('div');
|
||||||
|
installNotification.innerHTML = `
|
||||||
|
<div style="position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background: white; padding: 1rem; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); z-index: 1001; text-align: center;">
|
||||||
|
<p style="margin-bottom: 1rem; color: #374151;">Install eSPJ untuk pengalaman yang lebih baik</p>
|
||||||
|
<button onclick="installPWA()" style="background: linear-gradient(135deg, #fb923c, #f59e42); color: white; border: none; padding: 0.75rem 1.5rem; border-radius: 8px; margin-right: 0.5rem; cursor: pointer;">Install</button>
|
||||||
|
<button onclick="this.parentElement.parentElement.remove()" style="background: #6b7280; color: white; border: none; padding: 0.75rem 1.5rem; border-radius: 8px; cursor: pointer;">Nanti</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
document.body.appendChild(installNotification);
|
||||||
|
}
|
||||||
|
|
||||||
|
function installPWA() {
|
||||||
|
if (deferredPrompt) {
|
||||||
|
deferredPrompt.prompt();
|
||||||
|
deferredPrompt.userChoice.then((result) => {
|
||||||
|
if (result.outcome === 'accepted') {
|
||||||
|
console.log('User accepted the install prompt');
|
||||||
|
showNotification('Aplikasi berhasil diinstall!', 'success');
|
||||||
|
}
|
||||||
|
deferredPrompt = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Admin/Transport/SpjDriverUpst/Shared/_Layout.cshtml";
|
||||||
|
ViewData["Title"] = "Profil Page";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="max-w-sm mx-auto bg-white min-h-screen">
|
||||||
|
<!-- Header with Orange Background -->
|
||||||
|
<div class="bg-orange-500 text-white px-3 py-4 rounded-b-2xl relative pb-12">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<a href="@Url.Action("Index", "Home")" class="p-1 hover:bg-white/10 rounded-full transition-colors">
|
||||||
|
<i class="w-5 h-5" data-lucide="chevron-left"></i>
|
||||||
|
</a>
|
||||||
|
<h1 class="text-lg font-bold">Profil</h1>
|
||||||
|
<div class="w-8"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Profile Picture Section - Overlapping -->
|
||||||
|
<div class="flex justify-center -mt-12 mb-6 relative z-10">
|
||||||
|
<div class="relative">
|
||||||
|
<!-- Profile Picture -->
|
||||||
|
<div class="w-20 h-20 rounded-full border-2 border-white shadow-xl overflow-hidden bg-gray-200">
|
||||||
|
<img src="https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop&crop=face"
|
||||||
|
alt="Profile Picture"
|
||||||
|
class="w-full h-full object-cover">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Icon -->
|
||||||
|
<button class="absolute bottom-1 right-1 w-7 h-7 bg-white rounded-full shadow-lg flex items-center justify-center border border-gray-100 hover:bg-gray-50 transition-colors">
|
||||||
|
<i class="w-4 h-4 text-gray-600" data-lucide="edit-2"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Profile Content -->
|
||||||
|
<div class="p-4 space-y-4 bg-white border border-gray-200 mx-4 rounded-2xl">
|
||||||
|
<!-- Info Akun Header -->
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<h2 class="text-lg font-bold text-gray-800">Info Akun</h2>
|
||||||
|
<button class="text-orange-500 font-medium hover:text-orange-600 transition-colors text-sm">
|
||||||
|
Ubah
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Profile Information -->
|
||||||
|
<div class="space-y-4">
|
||||||
|
<!-- Nama -->
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div class="w-8 h-8 bg-gray-100 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
|
||||||
|
<i class="w-4 h-4 text-gray-600" data-lucide="user"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<p class="text-xs text-gray-500 mb-0.5">Nama</p>
|
||||||
|
<p class="text-base font-semibold text-gray-800">Bonny Agung Putra</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Email -->
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div class="w-8 h-8 bg-gray-100 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
|
||||||
|
<i class="w-4 h-4 text-gray-600" data-lucide="mail"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<p class="text-xs text-gray-500 mb-0.5">E-mail</p>
|
||||||
|
<p class="text-base font-semibold text-gray-800">bonny@gmail.com</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Phone -->
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div class="w-8 h-8 bg-gray-100 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
|
||||||
|
<i class="w-4 h-4 text-gray-600" data-lucide="phone"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<p class="text-xs text-gray-500 mb-0.5">No. HP</p>
|
||||||
|
<p class="text-base font-semibold text-gray-800">+6285777777777</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Address -->
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<div class="w-8 h-8 bg-gray-100 rounded-full flex items-center justify-center flex-shrink-0 mt-1">
|
||||||
|
<i class="w-4 h-4 text-gray-600" data-lucide="map-pin"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<p class="text-xs text-gray-500 mb-0.5">Alamat</p>
|
||||||
|
<p class="text-base font-semibold text-gray-800 leading-relaxed">
|
||||||
|
Kp. Pertanian II Rt 004 Rw 001 Kel. Klender Kec, Duren Swakit, Kota Adm. Jakarta Timur 13470
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- Logout Button -->
|
||||||
|
<div class="mx-4 pt-4 pb-4">
|
||||||
|
<button class="w-full bg-orange-500 hover:bg-orange-600 text-white py-3 rounded-xl font-bold text-base shadow-lg transition-all duration-200 hover:scale-105">
|
||||||
|
Keluar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Bottom Navigation -->
|
||||||
|
<partial name="~/Views/Admin/Transport/SpjDriverUpst/Shared/Components/_Navigation.cshtml" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
<div class="fixed bottom-0 left-1/2 transform -translate-x-1/2 w-full lg:max-w-sm z-99">
|
||||||
|
<div class="relative backdrop-blur-lg border border-gray-200/50 rounded-t-3xl shadow-xl overflow-hidden">
|
||||||
|
<div class="absolute -top-0 left-1/2 transform -translate-x-1/2 w-20 h-10">
|
||||||
|
<div class="w-full h-full bg-transparent relative">
|
||||||
|
<div class="absolute left-0 top-0 w-10 h-10 backdrop-blur-lg rounded-br-full"></div>
|
||||||
|
<div class="absolute right-0 top-0 w-10 h-10 backdrop-blur-lg rounded-bl-full"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-between items-center px-8 relative pt-6">
|
||||||
|
<a href="@Url.Action("Index", "Home")" class="flex flex-col items-center gap-1 px-4 py-2 transition-all duration-300 hover:scale-105 group">
|
||||||
|
<div class="relative">
|
||||||
|
<i class="w-6 h-6 text-gray-400 group-hover:text-gray-500 transition-colors duration-300" data-lucide="home"></i>
|
||||||
|
<div class="absolute -bottom-0.5 left-1/2 transform -translate-x-1/2 w-0 h-0.5 bg-upst group-hover:w-full transition-all duration-300"></div>
|
||||||
|
</div>
|
||||||
|
<span class="text-xs text-gray-600 group-hover:text-gray-500 font-medium transition-colors duration-300">Home</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="w-12"></div>
|
||||||
|
|
||||||
|
|
||||||
|
<a href="@Url.Action("Index", "History")" class="flex flex-col items-center gap-1 px-4 py-2 transition-all duration-300 hover:scale-105 group">
|
||||||
|
<div class="relative">
|
||||||
|
<i class="w-6 h-6 text-gray-400 group-hover:text-gray-500 transition-colors duration-300" data-lucide="clipboard-check"></i>
|
||||||
|
<div class="absolute -bottom-0.5 left-1/2 transform -translate-x-1/2 w-0 h-0.5 bg-upst group-hover:w-full transition-all duration-300"></div>
|
||||||
|
</div>
|
||||||
|
<span class="text-xs text-gray-600 group-hover:text-gray-500 font-medium transition-colors duration-300">History</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="absolute -top-4 left-1/2 transform -translate-x-1/2">
|
||||||
|
<a href="@Url.Action("Index", "Submit")" id="odoBtn" class="hover:cursor-pointer w-14 h-14 bg-upst rounded-full shadow-xl flex items-center justify-center transition-all duration-300 hover:scale-110 hover:rotate-6 border-4 border-white ring-2 ring-gray-200">
|
||||||
|
<i class="w-6 h-6 text-white" data-lucide="truck"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
@* <!-- ini untuk yang struk -->
|
||||||
|
<a href="@Url.Action("Struk", "Submit")" id="strukBtn" class="hover:cursor-pointer w-14 h-14 bg-upst rounded-full shadow-xl flex items-center justify-center transition-all duration-300 hover:scale-110 hover:rotate-6 border-4 border-white ring-2 ring-orange-200">
|
||||||
|
<i class="w-6 h-6 text-white" data-lucide="file-text"></i>
|
||||||
|
</a> *@
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="h-30"></div>
|
||||||
|
|
||||||
|
|
||||||
|
<register-block dynamic-section="scripts" key="jsNav">
|
||||||
|
|
||||||
|
</register-block>
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
<div class="fixed bottom-0 left-1/2 transform -translate-x-1/2 w-full max-w-sm z-99">
|
||||||
|
<div class="relative backdrop-blur-lg border border-gray-200/50 rounded-t-3xl shadow-xl overflow-hidden">
|
||||||
|
<div class="absolute -top-0 left-1/2 transform -translate-x-1/2 w-20 h-10">
|
||||||
|
<div class="w-full h-full bg-transparent relative">
|
||||||
|
<div class="absolute left-0 top-0 w-10 h-10 backdrop-blur-lg rounded-br-full"></div>
|
||||||
|
<div class="absolute right-0 top-0 w-10 h-10 backdrop-blur-lg rounded-bl-full"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigation Content -->
|
||||||
|
<div class="flex justify-between items-center px-8 relative pt-6">
|
||||||
|
<!-- Home Button -->
|
||||||
|
<a href="@Url.Action("Kosong", "Home")" class="flex flex-col items-center gap-1 px-4 py-2 transition-all duration-300 hover:scale-105 group">
|
||||||
|
<div class="relative">
|
||||||
|
<i class="w-6 h-6 text-gray-400 group-hover:text-orange-500 transition-colors duration-300" data-lucide="home"></i>
|
||||||
|
<div class="absolute -bottom-0.5 left-1/2 transform -translate-x-1/2 w-0 h-0.5 bg-orange-500 group-hover:w-full transition-all duration-300"></div>
|
||||||
|
</div>
|
||||||
|
<span class="text-xs text-gray-600 group-hover:text-orange-500 font-medium transition-colors duration-300">Home</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="w-12"></div>
|
||||||
|
|
||||||
|
<!-- Profile Button -->
|
||||||
|
|
||||||
|
<a href="@Url.Action("Index", "History")" class="flex flex-col items-center gap-1 px-4 py-2 transition-all duration-300 hover:scale-105 group">
|
||||||
|
<div class="relative">
|
||||||
|
<i class="w-6 h-6 text-gray-400 group-hover:text-orange-500 transition-colors duration-300" data-lucide="clipboard-check"></i>
|
||||||
|
<div class="absolute -bottom-0.5 left-1/2 transform -translate-x-1/2 w-0 h-0.5 bg-upst group-hover:w-full transition-all duration-300"></div>
|
||||||
|
</div>
|
||||||
|
<span class="text-xs text-gray-600 group-hover:text-orange-500 font-medium transition-colors duration-300">History</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Center Submit -->
|
||||||
|
<div class="absolute -top-4 left-1/2 transform -translate-x-1/2">
|
||||||
|
<a href="@Url.Action("Index", "Scan")" id="odoBtn" class="hover:cursor-pointer w-14 h-14 bg-upst rounded-full shadow-xl flex items-center justify-center transition-all duration-300 hover:scale-110 hover:rotate-6 border-4 border-white ring-2 ring-orange-200">
|
||||||
|
<i class="w-6 h-6 text-white" data-lucide="camera"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="h-30"></div>
|
||||||
|
|
||||||
|
|
||||||
|
<register-block dynamic-section="scripts" key="jsNav">
|
||||||
|
|
||||||
|
</register-block>
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
@model ErrorViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Error";
|
||||||
|
}
|
||||||
|
|
||||||
|
<h1 class="text-danger">Error.</h1>
|
||||||
|
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||||
|
|
||||||
|
@if (Model.ShowRequestId)
|
||||||
|
{
|
||||||
|
<p>
|
||||||
|
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
<h3>Development Mode</h3>
|
||||||
|
<p>
|
||||||
|
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||||
|
It can result in displaying sensitive information from exceptions to end users.
|
||||||
|
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||||
|
and restarting the app.
|
||||||
|
</p>
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>@ViewData["Title"] - eSPJ</title>
|
||||||
|
<script type="importmap"></script>
|
||||||
|
|
||||||
|
<!-- Theme Colors -->
|
||||||
|
<meta name="theme-color" content="#f97316">
|
||||||
|
<meta name="msapplication-navbutton-color" content="#f97316">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||||
|
|
||||||
|
<!-- Mobile Optimization -->
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
|
||||||
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="SPJ Angkut">
|
||||||
|
|
||||||
|
<!-- iOS Icons -->
|
||||||
|
<link rel="apple-touch-icon" href="~/icons/icon-152x152.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="~/icons/icon-180x180.png">
|
||||||
|
|
||||||
|
<!-- Windows Tiles -->
|
||||||
|
<meta name="msapplication-TileImage" content="/icons/icon-144x144.png">
|
||||||
|
<meta name="msapplication-TileColor" content="#f97316">
|
||||||
|
|
||||||
|
<link rel="manifest" href="@Url.Content("~/driver/manifest.json")" />
|
||||||
|
@* <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" /> *@
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter+Tight:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="@Url.Content("~/driver/css/watch.css")" asp-append-version="true" />
|
||||||
|
<link rel="stylesheet" href="@Url.Content("~/driver/css/website.css")" asp-append-version="true" />
|
||||||
|
<link rel="stylesheet" href="@Url.Content("~/driver/css/leaflet.css")" asp-append-version="true" />
|
||||||
|
<link rel="stylesheet" href="@Url.Content("~/driver/eSPJ.styles.css")" asp-append-version="true" />
|
||||||
|
@await RenderSectionAsync("Styles", required: false)
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="bg-gray-100">
|
||||||
|
|
||||||
|
@RenderBody()
|
||||||
|
|
||||||
|
<script src="@Url.Content("~/driver/lib/jquery/dist/jquery.min.js")"></script>
|
||||||
|
<script src="@Url.Content("~/driver/lib/bootstrap/dist/js/bootstrap.bundle.min.js")"></script>
|
||||||
|
<script src="@Url.Content("~/driver/js/site.js")">" asp-append-version="true"></script>
|
||||||
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
lucide.createIcons();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@await RenderSectionAsync("Scripts", required: false)
|
||||||
|
<dynamic-section name="scripts" />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
/* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
|
||||||
|
for details on configuring this project to bundle and minify static web assets. */
|
||||||
|
|
||||||
|
a.navbar-brand {
|
||||||
|
white-space: normal;
|
||||||
|
text-align: center;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #0077cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #1b6ec2;
|
||||||
|
border-color: #1861ac;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #1b6ec2;
|
||||||
|
border-color: #1861ac;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-top {
|
||||||
|
border-top: 1px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
.border-bottom {
|
||||||
|
border-bottom: 1px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box-shadow {
|
||||||
|
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.accept-policy {
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
line-height: 60px;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
|
||||||
|
<script src="~/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js"></script>
|
||||||
|
|
@ -0,0 +1,503 @@
|
||||||
|
@{
|
||||||
|
Layout = "~/Views/Admin/Transport/SpjDriver/Shared/_Layout.cshtml";
|
||||||
|
ViewData["Title"] = "Submit Foto Muatan";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="w-full lg:max-w-sm mx-auto bg-white min-h-screen">
|
||||||
|
<div class="bg-upst text-white px-3 py-4 rounded-b-2xl relative pb-12">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<a href="@Url.Action("Index", "Home")" class="p-1 hover:bg-white/10 rounded-full transition-colors">
|
||||||
|
<i class="w-5 h-5" data-lucide="chevron-left"></i>
|
||||||
|
</a>
|
||||||
|
<h1 class="text-lg font-bold">Unggah Foto Muatan</h1>
|
||||||
|
<div class="w-8"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="px-4 py-6 -mt-6 relative z-10">
|
||||||
|
<div class="bg-white rounded-2xl p-5 border border-gray-100">
|
||||||
|
<div class="flex items-center gap-3 mb-4">
|
||||||
|
<div class="w-12 h-12 bg-upst/10 rounded-2xl flex items-center justify-center">
|
||||||
|
<i class="w-6 h-6 text-upst" data-lucide="camera"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 class="text-lg font-bold text-gray-900">Foto Muatan Kendaraan</h2>
|
||||||
|
<p class="text-xs text-gray-500 flex items-center gap-1">
|
||||||
|
<i class="w-3 h-3" data-lucide="info"></i>
|
||||||
|
Optional
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="upload-area border-2 border-dashed border-gray-300 rounded-xl p-6 text-center hover:border-upst transition-colors cursor-pointer" id="upload-area">
|
||||||
|
<input type="file" name="FotoKondisiKendaraan" accept="image/jpeg,image/png" class="hidden" id="foto-upload">
|
||||||
|
<label for="foto-upload" class="cursor-pointer w-full block">
|
||||||
|
<div class="relative w-full h-60 bg-center bg-gray-100 rounded-xl flex items-center justify-center mx-auto mb-3 overflow-hidden preview-container" id="preview-container">
|
||||||
|
<div id="default-state">
|
||||||
|
<div class="upload-icon-container">
|
||||||
|
<img class="object-cover" src="~/driver/images/trukk.jpg" alt="contoh gambar">
|
||||||
|
<p class="absolute inset-0 flex items-center justify-center text-white bg-upst/40">Contoh Foto</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<img id="preview-image" src="#" alt="Preview" style="display:none;position:absolute;top:0;left:0;width:100%;height:100%;object-fit:cover;" />
|
||||||
|
|
||||||
|
<div class="preview-overlay" id="preview-overlay" style="display:none;">
|
||||||
|
<button type="button" class="overlay-btn" id="edit-preview" aria-label="Edit gambar">
|
||||||
|
<i class="w-5 h-5" data-lucide="edit-3"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="overlay-btn delete" id="close-preview" aria-label="Hapus gambar">
|
||||||
|
<i class="w-5 h-5" data-lucide="trash-2"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="file-info" id="file-info" style="display:none;">
|
||||||
|
<span id="file-name">image.jpg</span> • <span id="file-size">0 MB</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="upload-progress" id="upload-progress"></div>
|
||||||
|
</div>
|
||||||
|
<div id="upload-text">
|
||||||
|
<p class="text-sm text-gray-600 font-medium">Tap untuk unggah foto</p>
|
||||||
|
<p class="text-xs text-gray-400 mt-1">atau drag & drop file di sini</p>
|
||||||
|
<p class="text-xs text-gray-400 mt-2 flex items-center justify-center gap-1">
|
||||||
|
<i class="w-3 h-3" data-lucide="file-type"></i>
|
||||||
|
JPG, JPEG, PNG maksimal 10MB
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6 mb-2">
|
||||||
|
<div class="location-badge rounded-2xl p-4">
|
||||||
|
<div class="flex items-center gap-2 mb-2">
|
||||||
|
<div class="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center">
|
||||||
|
<i class="w-4 h-4 text-blue-600" data-lucide="map-pin"></i>
|
||||||
|
</div>
|
||||||
|
<span class="text-sm font-medium text-gray-700">Lokasi Anda:</span>
|
||||||
|
<button type="button" id="refresh-location" class="ml-auto p-1 hover:bg-gray-100 rounded-full transition-colors">
|
||||||
|
<i class="w-4 h-4 text-gray-500" data-lucide="refresh-cw"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p id="userLocationSubmit" class="font-semibold text-xs tracking-wide cursor-pointer underline text-upst hover:opacity-80 transition mt-1 flex items-center gap-2">
|
||||||
|
<span>Mendeteksi lokasi...</span>
|
||||||
|
</p>
|
||||||
|
<p class="text-xs text-gray-400 mt-1">Klik lokasi di atas untuk update posisi Anda</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<form asp-action="UploadFotoMuatan" method="post" enctype="multipart/form-data" class="mt-6" id="upload-form">
|
||||||
|
<input type="hidden" name="Latitude" id="input-latitude" />
|
||||||
|
<input type="hidden" name="Longitude" id="input-longitude" />
|
||||||
|
<input type="hidden" name="AlamatJalan" id="input-alamat-jalan" />
|
||||||
|
<div class="flex gap-3 pt-4">
|
||||||
|
<button type="submit" id="submit-btn"
|
||||||
|
class="floating-button flex-1 bg-upst text-white font-semibold py-3 rounded-xl hover:opacity-90 transition-all duration-200 shadow-lg flex items-center justify-center gap-2">
|
||||||
|
<i class="w-5 h-5" data-lucide="upload"></i>
|
||||||
|
<span>Unggah</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<partial name="~/Views/Admin/Transport/SpjDriverUpst/Shared/Components/_Navigation.cshtml" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<register-block dynamic-section="styles" key="cssSubmit">
|
||||||
|
<style>
|
||||||
|
.upload-area {
|
||||||
|
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-area:hover {
|
||||||
|
background: rgba(var(--upst-rgb), 0.05);
|
||||||
|
border-color: #006B3F !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-area.dragover {
|
||||||
|
background: linear-gradient(135deg, #fef7ed 0%, #fed7aa 100%);
|
||||||
|
border-color: #f97316 !important;
|
||||||
|
transform: scale(1.02);
|
||||||
|
box-shadow: 0 25px 50px -12px rgba(251, 146, 60, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-container {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16px;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-container:hover .preview-overlay {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-btn {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-btn.delete:hover {
|
||||||
|
background: rgba(239, 68, 68, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-progress {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 4px;
|
||||||
|
background: linear-gradient(90deg, #f97316, #fb923c);
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
border-radius: 0 0 12px 12px;
|
||||||
|
width: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pulse-animation {
|
||||||
|
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: .5; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-animation {
|
||||||
|
animation: successPulse 0.6s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes successPulse {
|
||||||
|
0% { transform: scale(1); }
|
||||||
|
50% { transform: scale(1.05); }
|
||||||
|
100% { transform: scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-info {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 8px;
|
||||||
|
left: 8px;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
color: white;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 10px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-container:hover .file-info {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-badge {
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-button {
|
||||||
|
box-shadow: 0 10px 25px -5px rgba(251, 146, 60, 0.4);
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-button:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 20px 40px -5px rgba(251, 146, 60, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-icon-container {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
background: linear-gradient(135deg, #fef7ed 0%, #fed7aa 100%);
|
||||||
|
border-radius: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0 auto 16px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-area:hover .upload-icon-container {
|
||||||
|
transform: scale(1.1);
|
||||||
|
background: linear-gradient(135deg, #fb923c 0%, #f97316 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-area:hover .upload-icon-container i {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</register-block>
|
||||||
|
|
||||||
|
<register-block dynamic-section="scripts" key="jsSubmit">
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const userLocationEl = document.getElementById("userLocationSubmit");
|
||||||
|
const inputLat = document.getElementById("input-latitude");
|
||||||
|
const inputLng = document.getElementById("input-longitude");
|
||||||
|
const inputAlamat = document.getElementById("input-alamat-jalan");
|
||||||
|
const refreshLocationBtn = document.getElementById("refresh-location");
|
||||||
|
|
||||||
|
const uploadArea = document.getElementById("upload-area");
|
||||||
|
const fotoInput = document.getElementById("foto-upload");
|
||||||
|
const previewImage = document.getElementById("preview-image");
|
||||||
|
const previewIcon = document.getElementById("preview-icon");
|
||||||
|
const closePreview = document.getElementById("close-preview");
|
||||||
|
const editPreview = document.getElementById("edit-preview");
|
||||||
|
const previewOverlay = document.getElementById("preview-overlay");
|
||||||
|
const defaultState = document.getElementById("default-state");
|
||||||
|
const uploadText = document.getElementById("upload-text");
|
||||||
|
const fileInfo = document.getElementById("file-info");
|
||||||
|
const fileName = document.getElementById("file-name");
|
||||||
|
const fileSize = document.getElementById("file-size");
|
||||||
|
const uploadProgress = document.getElementById("upload-progress");
|
||||||
|
const submitBtn = document.getElementById("submit-btn");
|
||||||
|
|
||||||
|
uploadArea.addEventListener("dragover", function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
uploadArea.classList.add("dragover");
|
||||||
|
});
|
||||||
|
|
||||||
|
uploadArea.addEventListener("dragleave", function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
uploadArea.classList.remove("dragover");
|
||||||
|
});
|
||||||
|
|
||||||
|
uploadArea.addEventListener("drop", function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
uploadArea.classList.remove("dragover");
|
||||||
|
const files = e.dataTransfer.files;
|
||||||
|
if (files.length > 0) {
|
||||||
|
handleFileSelect(files[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
uploadArea.addEventListener("click", function(e) {
|
||||||
|
if (e.target.tagName !== 'LABEL' && !e.target.closest('label')) {
|
||||||
|
fotoInput.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
fotoInput.addEventListener("change", function(e) {
|
||||||
|
const file = e.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
handleFileSelect(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleFileSelect(file) {
|
||||||
|
const allowedTypes = ["image/jpeg", "image/png", "image/jpg"];
|
||||||
|
if (!allowedTypes.includes(file.type)) {
|
||||||
|
showAlert("File harus JPG, JPEG, atau PNG.", "error");
|
||||||
|
fotoInput.value = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (file.size > 10 * 1024 * 1024) {
|
||||||
|
showAlert("Ukuran maksimal 10MB.", "error");
|
||||||
|
fotoInput.value = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showUploadProgress(file);
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function(ev) {
|
||||||
|
previewImage.src = ev.target.result;
|
||||||
|
fileName.textContent = file.name;
|
||||||
|
fileSize.textContent = formatFileSize(file.size);
|
||||||
|
|
||||||
|
let progress = 0;
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
progress += Math.random() * 15;
|
||||||
|
if (progress >= 100) {
|
||||||
|
progress = 100;
|
||||||
|
clearInterval(interval);
|
||||||
|
setTimeout(() => {
|
||||||
|
showPreview();
|
||||||
|
uploadProgress.style.width = "0%";
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
uploadProgress.style.width = progress + "%";
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showUploadProgress(file) {
|
||||||
|
defaultState.style.display = "none";
|
||||||
|
uploadText.style.display = "none";
|
||||||
|
uploadProgress.style.width = "0%";
|
||||||
|
}
|
||||||
|
|
||||||
|
function showPreview() {
|
||||||
|
previewImage.style.display = "block";
|
||||||
|
previewOverlay.style.display = "flex";
|
||||||
|
fileInfo.style.display = "block";
|
||||||
|
uploadArea.classList.add("success-animation");
|
||||||
|
showAlert("Foto berhasil dimuat!", "success");
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetUpload() {
|
||||||
|
fotoInput.value = "";
|
||||||
|
previewImage.src = "#";
|
||||||
|
previewImage.style.display = "none";
|
||||||
|
previewOverlay.style.display = "none";
|
||||||
|
fileInfo.style.display = "none";
|
||||||
|
defaultState.style.display = "block";
|
||||||
|
uploadText.style.display = "block";
|
||||||
|
uploadArea.classList.remove("success-animation");
|
||||||
|
uploadProgress.style.width = "0%";
|
||||||
|
}
|
||||||
|
|
||||||
|
editPreview.addEventListener("click", function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
fotoInput.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
closePreview.addEventListener("click", function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
resetUpload();
|
||||||
|
});
|
||||||
|
|
||||||
|
function reverseGeocode(lat, lng) {
|
||||||
|
fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
const address = data.display_name || `${lat}, ${lng}`;
|
||||||
|
userLocationEl.innerHTML = `<span>${address}</span>`;
|
||||||
|
localStorage.setItem("user_latitude", lat);
|
||||||
|
localStorage.setItem("user_longitude", lng);
|
||||||
|
localStorage.setItem("user_address", address);
|
||||||
|
if (inputLat) inputLat.value = lat;
|
||||||
|
if (inputLng) inputLng.value = lng;
|
||||||
|
if (inputAlamat) inputAlamat.value = address;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
userLocationEl.innerHTML = `<span>${lat}, ${lng}</span>`;
|
||||||
|
if (inputLat) inputLat.value = lat;
|
||||||
|
if (inputLng) inputLng.value = lng;
|
||||||
|
if (inputAlamat) inputAlamat.value = `${lat}, ${lng}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocationUpdate() {
|
||||||
|
if ("geolocation" in navigator) {
|
||||||
|
userLocationEl.innerHTML = `<span>Mendeteksi lokasi...</span>`;
|
||||||
|
navigator.geolocation.getCurrentPosition(
|
||||||
|
function (position) {
|
||||||
|
const lat = position.coords.latitude.toFixed(6);
|
||||||
|
const lng = position.coords.longitude.toFixed(6);
|
||||||
|
reverseGeocode(lat, lng);
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
userLocationEl.innerHTML = `<span>Lokasi tidak diizinkan</span>`;
|
||||||
|
if (inputLat) inputLat.value = "";
|
||||||
|
if (inputLng) inputLng.value = "";
|
||||||
|
if (inputAlamat) inputAlamat.value = "Lokasi tidak diizinkan";
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
userLocationEl.innerHTML = `<span>Browser tidak mendukung lokasi</span>`;
|
||||||
|
if (inputLat) inputLat.value = "";
|
||||||
|
if (inputLng) inputLng.value = "";
|
||||||
|
if (inputAlamat) inputAlamat.value = "Browser tidak mendukung lokasi";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getLocationUpdate();
|
||||||
|
|
||||||
|
userLocationEl.addEventListener("click", function () {
|
||||||
|
getLocationUpdate();
|
||||||
|
});
|
||||||
|
|
||||||
|
refreshLocationBtn.addEventListener("click", function() {
|
||||||
|
getLocationUpdate();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("upload-form").addEventListener("submit", function(e) {
|
||||||
|
const originalContent = submitBtn.innerHTML;
|
||||||
|
submitBtn.innerHTML = `
|
||||||
|
<svg class="animate-spin w-5 h-5 mr-2" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
|
<path class="opacity-75" fill="currentColor" d="m4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
|
</svg>
|
||||||
|
<span>Mengunggah...</span>
|
||||||
|
`;
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
submitBtn.innerHTML = originalContent;
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
showAlert("Form berhasil dikirim!", "success");
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
function formatFileSize(bytes) {
|
||||||
|
if (bytes === 0) return '0 Bytes';
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAlert(message, type) {
|
||||||
|
const alertDiv = document.createElement('div');
|
||||||
|
alertDiv.className = `fixed top-4 right-4 z-50 p-4 rounded-lg text-white font-medium transition-all duration-300 ${
|
||||||
|
type === 'success' ? 'bg-green-500' : 'bg-red-500'
|
||||||
|
}`;
|
||||||
|
alertDiv.textContent = message;
|
||||||
|
alertDiv.style.transform = 'translateX(100%)';
|
||||||
|
|
||||||
|
document.body.appendChild(alertDiv);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
alertDiv.style.transform = 'translateX(0)';
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
alertDiv.style.transform = 'translateX(100%)';
|
||||||
|
setTimeout(() => {
|
||||||
|
if (document.body.contains(alertDiv)) {
|
||||||
|
document.body.removeChild(alertDiv);
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</register-block>
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -8,5 +8,11 @@
|
||||||
"SSO": {
|
"SSO": {
|
||||||
"LoginUrl": "https://akun.dinaslhdki.id/Identity/Account/Login?ReturnUrl=%2Fconnect%2Fauthorize%3Fclient_id%3Dwebdinas%26redirect_uri%3Dhttps%253A%252F%252Flingkunganhidup.jakarta.go.id%252Fsignin-oidc%26response_type%3Dcode%26scope%3Dopenid%2520profile%2520email%2520roles%26code_challenge%3Df9YuMeOzpB-egjQlGp4Pqrthdewj6YeINPhz7wgbL-k%26code_challenge_method%3DS256%26response_mode%3Dform_post%26nonce%3D638893657991954291.YTQ5OGU1NWEtOGU0Yi00NjI2LWFkOGEtZjI0YzliMWE5ZGJmYzk1NWFmM2QtOTA3YS00YmU4LWIwYmYtMjBhODc3M2Q1Mjll%26state%3DCfDJ8MtdNDKU3ypIhY_fd6D9SIg-h4wZ5PTm8sXsF0Qt60PKRgGw0d3i7fDi1lkDFBBsDPqzCl_2wM0_cfa16rr1BLmzplWuTtyIwTeTQKD6L-hhysUTyV94E2A1nocB5y-bM1hor2UaCtT9qs7LbdkFPGgUjV6ijoL0HcjilJtVzWYIo6aSsmiEUti9Q8n7XNEEGaZIVLDUH_qfykx51FMn5RCO2j-FkuSA98WBt8KyiN4-jimbr_LTkJVFClnKy_ClAfTS1vlC2a2hu-dDOdCYqlnf6QfuSCvZBf_2D4geBWnlRIHM5m8PfmtYm_WgYyQMuqYf9zkxn2_FTcrMFl4dC5ypMX5yWm0GaeMJlpUt_QYGRyMX6blGcqw5VW9YIexCX9FDuD3xSIjCqnVn6digGLBkDZ8TghO6_KJ5Jkyg8hws%26x-client-SKU%3DID_NET9_0%26x-client-ver%3D8.0.1.0%26prompt%3D"
|
"LoginUrl": "https://akun.dinaslhdki.id/Identity/Account/Login?ReturnUrl=%2Fconnect%2Fauthorize%3Fclient_id%3Dwebdinas%26redirect_uri%3Dhttps%253A%252F%252Flingkunganhidup.jakarta.go.id%252Fsignin-oidc%26response_type%3Dcode%26scope%3Dopenid%2520profile%2520email%2520roles%26code_challenge%3Df9YuMeOzpB-egjQlGp4Pqrthdewj6YeINPhz7wgbL-k%26code_challenge_method%3DS256%26response_mode%3Dform_post%26nonce%3D638893657991954291.YTQ5OGU1NWEtOGU0Yi00NjI2LWFkOGEtZjI0YzliMWE5ZGJmYzk1NWFmM2QtOTA3YS00YmU4LWIwYmYtMjBhODc3M2Q1Mjll%26state%3DCfDJ8MtdNDKU3ypIhY_fd6D9SIg-h4wZ5PTm8sXsF0Qt60PKRgGw0d3i7fDi1lkDFBBsDPqzCl_2wM0_cfa16rr1BLmzplWuTtyIwTeTQKD6L-hhysUTyV94E2A1nocB5y-bM1hor2UaCtT9qs7LbdkFPGgUjV6ijoL0HcjilJtVzWYIo6aSsmiEUti9Q8n7XNEEGaZIVLDUH_qfykx51FMn5RCO2j-FkuSA98WBt8KyiN4-jimbr_LTkJVFClnKy_ClAfTS1vlC2a2hu-dDOdCYqlnf6QfuSCvZBf_2D4geBWnlRIHM5m8PfmtYm_WgYyQMuqYf9zkxn2_FTcrMFl4dC5ypMX5yWm0GaeMJlpUt_QYGRyMX6blGcqw5VW9YIexCX9FDuD3xSIjCqnVn6digGLBkDZ8TghO6_KJ5Jkyg8hws%26x-client-SKU%3DID_NET9_0%26x-client-ver%3D8.0.1.0%26prompt%3D"
|
||||||
},
|
},
|
||||||
|
"OpenRouter": {
|
||||||
|
"OCRkey": "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"
|
||||||
|
},
|
||||||
|
"Mapbox": {
|
||||||
|
"Apikey": "pk.eyJ1IjoibWFyc3pheW4iLCJhIjoiY21sajUyMDd0MDllcjNncGdna3NzeTR4ZyJ9.Wvbh5J94j9sF_8KNPp9FYQ"
|
||||||
|
},
|
||||||
"AllowedHosts": "*"
|
"AllowedHosts": "*"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
||||||
|
|
||||||
case `uname` in
|
|
||||||
*CYGWIN*|*MINGW*|*MSYS*)
|
|
||||||
if command -v cygpath > /dev/null 2>&1; then
|
|
||||||
basedir=`cygpath -w "$basedir"`
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ -x "$basedir/node" ]; then
|
|
||||||
exec "$basedir/node" "$basedir/../detect-libc/bin/detect-libc.js" "$@"
|
|
||||||
else
|
|
||||||
exec node "$basedir/../detect-libc/bin/detect-libc.js" "$@"
|
|
||||||
fi
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
@ECHO off
|
|
||||||
GOTO start
|
|
||||||
:find_dp0
|
|
||||||
SET dp0=%~dp0
|
|
||||||
EXIT /b
|
|
||||||
:start
|
|
||||||
SETLOCAL
|
|
||||||
CALL :find_dp0
|
|
||||||
|
|
||||||
IF EXIST "%dp0%\node.exe" (
|
|
||||||
SET "_prog=%dp0%\node.exe"
|
|
||||||
) ELSE (
|
|
||||||
SET "_prog=node"
|
|
||||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
|
||||||
)
|
|
||||||
|
|
||||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\detect-libc\bin\detect-libc.js" %*
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
#!/usr/bin/env pwsh
|
|
||||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
|
||||||
|
|
||||||
$exe=""
|
|
||||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
|
||||||
# Fix case when both the Windows and Linux builds of Node
|
|
||||||
# are installed in the same directory
|
|
||||||
$exe=".exe"
|
|
||||||
}
|
|
||||||
$ret=0
|
|
||||||
if (Test-Path "$basedir/node$exe") {
|
|
||||||
# Support pipeline input
|
|
||||||
if ($MyInvocation.ExpectingInput) {
|
|
||||||
$input | & "$basedir/node$exe" "$basedir/../detect-libc/bin/detect-libc.js" $args
|
|
||||||
} else {
|
|
||||||
& "$basedir/node$exe" "$basedir/../detect-libc/bin/detect-libc.js" $args
|
|
||||||
}
|
|
||||||
$ret=$LASTEXITCODE
|
|
||||||
} else {
|
|
||||||
# Support pipeline input
|
|
||||||
if ($MyInvocation.ExpectingInput) {
|
|
||||||
$input | & "node$exe" "$basedir/../detect-libc/bin/detect-libc.js" $args
|
|
||||||
} else {
|
|
||||||
& "node$exe" "$basedir/../detect-libc/bin/detect-libc.js" $args
|
|
||||||
}
|
|
||||||
$ret=$LASTEXITCODE
|
|
||||||
}
|
|
||||||
exit $ret
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
||||||
|
|
||||||
case `uname` in
|
|
||||||
*CYGWIN*|*MINGW*|*MSYS*)
|
|
||||||
if command -v cygpath > /dev/null 2>&1; then
|
|
||||||
basedir=`cygpath -w "$basedir"`
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ -x "$basedir/node" ]; then
|
|
||||||
exec "$basedir/node" "$basedir/../jiti/lib/jiti-cli.mjs" "$@"
|
|
||||||
else
|
|
||||||
exec node "$basedir/../jiti/lib/jiti-cli.mjs" "$@"
|
|
||||||
fi
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
../jiti/lib/jiti-cli.mjs
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
@ECHO off
|
|
||||||
GOTO start
|
|
||||||
:find_dp0
|
|
||||||
SET dp0=%~dp0
|
|
||||||
EXIT /b
|
|
||||||
:start
|
|
||||||
SETLOCAL
|
|
||||||
CALL :find_dp0
|
|
||||||
|
|
||||||
IF EXIST "%dp0%\node.exe" (
|
|
||||||
SET "_prog=%dp0%\node.exe"
|
|
||||||
) ELSE (
|
|
||||||
SET "_prog=node"
|
|
||||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
|
||||||
)
|
|
||||||
|
|
||||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\jiti\lib\jiti-cli.mjs" %*
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
#!/usr/bin/env pwsh
|
|
||||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
|
||||||
|
|
||||||
$exe=""
|
|
||||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
|
||||||
# Fix case when both the Windows and Linux builds of Node
|
|
||||||
# are installed in the same directory
|
|
||||||
$exe=".exe"
|
|
||||||
}
|
|
||||||
$ret=0
|
|
||||||
if (Test-Path "$basedir/node$exe") {
|
|
||||||
# Support pipeline input
|
|
||||||
if ($MyInvocation.ExpectingInput) {
|
|
||||||
$input | & "$basedir/node$exe" "$basedir/../jiti/lib/jiti-cli.mjs" $args
|
|
||||||
} else {
|
|
||||||
& "$basedir/node$exe" "$basedir/../jiti/lib/jiti-cli.mjs" $args
|
|
||||||
}
|
|
||||||
$ret=$LASTEXITCODE
|
|
||||||
} else {
|
|
||||||
# Support pipeline input
|
|
||||||
if ($MyInvocation.ExpectingInput) {
|
|
||||||
$input | & "node$exe" "$basedir/../jiti/lib/jiti-cli.mjs" $args
|
|
||||||
} else {
|
|
||||||
& "node$exe" "$basedir/../jiti/lib/jiti-cli.mjs" $args
|
|
||||||
}
|
|
||||||
$ret=$LASTEXITCODE
|
|
||||||
}
|
|
||||||
exit $ret
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
||||||
|
|
||||||
case `uname` in
|
|
||||||
*CYGWIN*|*MINGW*|*MSYS*)
|
|
||||||
if command -v cygpath > /dev/null 2>&1; then
|
|
||||||
basedir=`cygpath -w "$basedir"`
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ -x "$basedir/node" ]; then
|
|
||||||
exec "$basedir/node" "$basedir/../mkdirp/dist/cjs/src/bin.js" "$@"
|
|
||||||
else
|
|
||||||
exec node "$basedir/../mkdirp/dist/cjs/src/bin.js" "$@"
|
|
||||||
fi
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
@ECHO off
|
|
||||||
GOTO start
|
|
||||||
:find_dp0
|
|
||||||
SET dp0=%~dp0
|
|
||||||
EXIT /b
|
|
||||||
:start
|
|
||||||
SETLOCAL
|
|
||||||
CALL :find_dp0
|
|
||||||
|
|
||||||
IF EXIST "%dp0%\node.exe" (
|
|
||||||
SET "_prog=%dp0%\node.exe"
|
|
||||||
) ELSE (
|
|
||||||
SET "_prog=node"
|
|
||||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
|
||||||
)
|
|
||||||
|
|
||||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\mkdirp\dist\cjs\src\bin.js" %*
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
#!/usr/bin/env pwsh
|
|
||||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
|
||||||
|
|
||||||
$exe=""
|
|
||||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
|
||||||
# Fix case when both the Windows and Linux builds of Node
|
|
||||||
# are installed in the same directory
|
|
||||||
$exe=".exe"
|
|
||||||
}
|
|
||||||
$ret=0
|
|
||||||
if (Test-Path "$basedir/node$exe") {
|
|
||||||
# Support pipeline input
|
|
||||||
if ($MyInvocation.ExpectingInput) {
|
|
||||||
$input | & "$basedir/node$exe" "$basedir/../mkdirp/dist/cjs/src/bin.js" $args
|
|
||||||
} else {
|
|
||||||
& "$basedir/node$exe" "$basedir/../mkdirp/dist/cjs/src/bin.js" $args
|
|
||||||
}
|
|
||||||
$ret=$LASTEXITCODE
|
|
||||||
} else {
|
|
||||||
# Support pipeline input
|
|
||||||
if ($MyInvocation.ExpectingInput) {
|
|
||||||
$input | & "node$exe" "$basedir/../mkdirp/dist/cjs/src/bin.js" $args
|
|
||||||
} else {
|
|
||||||
& "node$exe" "$basedir/../mkdirp/dist/cjs/src/bin.js" $args
|
|
||||||
}
|
|
||||||
$ret=$LASTEXITCODE
|
|
||||||
}
|
|
||||||
exit $ret
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
|
||||||
|
|
||||||
case `uname` in
|
|
||||||
*CYGWIN*|*MINGW*|*MSYS*)
|
|
||||||
if command -v cygpath > /dev/null 2>&1; then
|
|
||||||
basedir=`cygpath -w "$basedir"`
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ -x "$basedir/node" ]; then
|
|
||||||
exec "$basedir/node" "$basedir/../@tailwindcss/cli/dist/index.mjs" "$@"
|
|
||||||
else
|
|
||||||
exec node "$basedir/../@tailwindcss/cli/dist/index.mjs" "$@"
|
|
||||||
fi
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
../@tailwindcss/cli/dist/index.mjs
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
@ECHO off
|
|
||||||
GOTO start
|
|
||||||
:find_dp0
|
|
||||||
SET dp0=%~dp0
|
|
||||||
EXIT /b
|
|
||||||
:start
|
|
||||||
SETLOCAL
|
|
||||||
CALL :find_dp0
|
|
||||||
|
|
||||||
IF EXIST "%dp0%\node.exe" (
|
|
||||||
SET "_prog=%dp0%\node.exe"
|
|
||||||
) ELSE (
|
|
||||||
SET "_prog=node"
|
|
||||||
SET PATHEXT=%PATHEXT:;.JS;=;%
|
|
||||||
)
|
|
||||||
|
|
||||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\@tailwindcss\cli\dist\index.mjs" %*
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
#!/usr/bin/env pwsh
|
|
||||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
|
|
||||||
|
|
||||||
$exe=""
|
|
||||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
|
|
||||||
# Fix case when both the Windows and Linux builds of Node
|
|
||||||
# are installed in the same directory
|
|
||||||
$exe=".exe"
|
|
||||||
}
|
|
||||||
$ret=0
|
|
||||||
if (Test-Path "$basedir/node$exe") {
|
|
||||||
# Support pipeline input
|
|
||||||
if ($MyInvocation.ExpectingInput) {
|
|
||||||
$input | & "$basedir/node$exe" "$basedir/../@tailwindcss/cli/dist/index.mjs" $args
|
|
||||||
} else {
|
|
||||||
& "$basedir/node$exe" "$basedir/../@tailwindcss/cli/dist/index.mjs" $args
|
|
||||||
}
|
|
||||||
$ret=$LASTEXITCODE
|
|
||||||
} else {
|
|
||||||
# Support pipeline input
|
|
||||||
if ($MyInvocation.ExpectingInput) {
|
|
||||||
$input | & "node$exe" "$basedir/../@tailwindcss/cli/dist/index.mjs" $args
|
|
||||||
} else {
|
|
||||||
& "node$exe" "$basedir/../@tailwindcss/cli/dist/index.mjs" $args
|
|
||||||
}
|
|
||||||
$ret=$LASTEXITCODE
|
|
||||||
}
|
|
||||||
exit $ret
|
|
||||||
|
|
@ -3,41 +3,26 @@
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"node_modules/@ampproject/remapping": {
|
|
||||||
"version": "2.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
|
||||||
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@jridgewell/gen-mapping": "^0.3.5",
|
|
||||||
"@jridgewell/trace-mapping": "^0.3.24"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@isaacs/fs-minipass": {
|
|
||||||
"version": "4.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
|
|
||||||
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"minipass": "^7.0.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@jridgewell/gen-mapping": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.12",
|
"version": "0.3.13",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
"integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
|
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||||
"@jridgewell/trace-mapping": "^0.3.24"
|
"@jridgewell/trace-mapping": "^0.3.24"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@jridgewell/remapping": {
|
||||||
|
"version": "2.3.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
||||||
|
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/gen-mapping": "^0.3.5",
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.24"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@jridgewell/resolve-uri": {
|
"node_modules/@jridgewell/resolve-uri": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||||
|
|
@ -48,15 +33,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/sourcemap-codec": {
|
"node_modules/@jridgewell/sourcemap-codec": {
|
||||||
"version": "1.5.4",
|
"version": "1.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||||
"integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
|
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/trace-mapping": {
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
"version": "0.3.29",
|
"version": "0.3.31",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||||
"integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
|
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/resolve-uri": "^3.1.0",
|
"@jridgewell/resolve-uri": "^3.1.0",
|
||||||
|
|
@ -64,16 +49,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@parcel/watcher": {
|
"node_modules/@parcel/watcher": {
|
||||||
"version": "2.5.1",
|
"version": "2.5.6",
|
||||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz",
|
||||||
"integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
|
"integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"detect-libc": "^1.0.3",
|
"detect-libc": "^2.0.3",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
"micromatch": "^4.0.5",
|
"node-addon-api": "^7.0.0",
|
||||||
"node-addon-api": "^7.0.0"
|
"picomatch": "^4.0.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10.0.0"
|
"node": ">= 10.0.0"
|
||||||
|
|
@ -83,32 +68,32 @@
|
||||||
"url": "https://opencollective.com/parcel"
|
"url": "https://opencollective.com/parcel"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@parcel/watcher-android-arm64": "2.5.1",
|
"@parcel/watcher-android-arm64": "2.5.6",
|
||||||
"@parcel/watcher-darwin-arm64": "2.5.1",
|
"@parcel/watcher-darwin-arm64": "2.5.6",
|
||||||
"@parcel/watcher-darwin-x64": "2.5.1",
|
"@parcel/watcher-darwin-x64": "2.5.6",
|
||||||
"@parcel/watcher-freebsd-x64": "2.5.1",
|
"@parcel/watcher-freebsd-x64": "2.5.6",
|
||||||
"@parcel/watcher-linux-arm-glibc": "2.5.1",
|
"@parcel/watcher-linux-arm-glibc": "2.5.6",
|
||||||
"@parcel/watcher-linux-arm-musl": "2.5.1",
|
"@parcel/watcher-linux-arm-musl": "2.5.6",
|
||||||
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
|
"@parcel/watcher-linux-arm64-glibc": "2.5.6",
|
||||||
"@parcel/watcher-linux-arm64-musl": "2.5.1",
|
"@parcel/watcher-linux-arm64-musl": "2.5.6",
|
||||||
"@parcel/watcher-linux-x64-glibc": "2.5.1",
|
"@parcel/watcher-linux-x64-glibc": "2.5.6",
|
||||||
"@parcel/watcher-linux-x64-musl": "2.5.1",
|
"@parcel/watcher-linux-x64-musl": "2.5.6",
|
||||||
"@parcel/watcher-win32-arm64": "2.5.1",
|
"@parcel/watcher-win32-arm64": "2.5.6",
|
||||||
"@parcel/watcher-win32-ia32": "2.5.1",
|
"@parcel/watcher-win32-ia32": "2.5.6",
|
||||||
"@parcel/watcher-win32-x64": "2.5.1"
|
"@parcel/watcher-win32-x64": "2.5.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@parcel/watcher-win32-x64": {
|
"node_modules/@parcel/watcher-linux-x64-glibc": {
|
||||||
"version": "2.5.1",
|
"version": "2.5.6",
|
||||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz",
|
||||||
"integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
|
"integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10.0.0"
|
"node": ">= 10.0.0"
|
||||||
|
|
@ -119,149 +104,99 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/cli": {
|
"node_modules/@tailwindcss/cli": {
|
||||||
"version": "4.1.11",
|
"version": "4.1.18",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.1.18.tgz",
|
||||||
"integrity": "sha512-7RAFOrVaXCFz5ooEG36Kbh+sMJiI2j4+Ozp71smgjnLfBRu7DTfoq8DsTvzse2/6nDeo2M3vS/FGaxfDgr3rtQ==",
|
"integrity": "sha512-sMZ+lZbDyxwjD2E0L7oRUjJ01Ffjtme5OtjvvnC+cV4CEDcbqzbp25TCpxHj6kWLU9+DlqJOiNgSOgctC2aZmg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@parcel/watcher": "^2.5.1",
|
"@parcel/watcher": "^2.5.1",
|
||||||
"@tailwindcss/node": "4.1.11",
|
"@tailwindcss/node": "4.1.18",
|
||||||
"@tailwindcss/oxide": "4.1.11",
|
"@tailwindcss/oxide": "4.1.18",
|
||||||
"enhanced-resolve": "^5.18.1",
|
"enhanced-resolve": "^5.18.3",
|
||||||
"mri": "^1.2.0",
|
"mri": "^1.2.0",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
"tailwindcss": "4.1.11"
|
"tailwindcss": "4.1.18"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"tailwindcss": "dist/index.mjs"
|
"tailwindcss": "dist/index.mjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/node": {
|
"node_modules/@tailwindcss/node": {
|
||||||
"version": "4.1.11",
|
"version": "4.1.18",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz",
|
||||||
"integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==",
|
"integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.3.0",
|
"@jridgewell/remapping": "^2.3.4",
|
||||||
"enhanced-resolve": "^5.18.1",
|
"enhanced-resolve": "^5.18.3",
|
||||||
"jiti": "^2.4.2",
|
"jiti": "^2.6.1",
|
||||||
"lightningcss": "1.30.1",
|
"lightningcss": "1.30.2",
|
||||||
"magic-string": "^0.30.17",
|
"magic-string": "^0.30.21",
|
||||||
"source-map-js": "^1.2.1",
|
"source-map-js": "^1.2.1",
|
||||||
"tailwindcss": "4.1.11"
|
"tailwindcss": "4.1.18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide": {
|
"node_modules/@tailwindcss/oxide": {
|
||||||
"version": "4.1.11",
|
"version": "4.1.18",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz",
|
||||||
"integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==",
|
"integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==",
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
|
||||||
"detect-libc": "^2.0.4",
|
|
||||||
"tar": "^7.4.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@tailwindcss/oxide-android-arm64": "4.1.11",
|
"@tailwindcss/oxide-android-arm64": "4.1.18",
|
||||||
"@tailwindcss/oxide-darwin-arm64": "4.1.11",
|
"@tailwindcss/oxide-darwin-arm64": "4.1.18",
|
||||||
"@tailwindcss/oxide-darwin-x64": "4.1.11",
|
"@tailwindcss/oxide-darwin-x64": "4.1.18",
|
||||||
"@tailwindcss/oxide-freebsd-x64": "4.1.11",
|
"@tailwindcss/oxide-freebsd-x64": "4.1.18",
|
||||||
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11",
|
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18",
|
||||||
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.11",
|
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.18",
|
||||||
"@tailwindcss/oxide-linux-arm64-musl": "4.1.11",
|
"@tailwindcss/oxide-linux-arm64-musl": "4.1.18",
|
||||||
"@tailwindcss/oxide-linux-x64-gnu": "4.1.11",
|
"@tailwindcss/oxide-linux-x64-gnu": "4.1.18",
|
||||||
"@tailwindcss/oxide-linux-x64-musl": "4.1.11",
|
"@tailwindcss/oxide-linux-x64-musl": "4.1.18",
|
||||||
"@tailwindcss/oxide-wasm32-wasi": "4.1.11",
|
"@tailwindcss/oxide-wasm32-wasi": "4.1.18",
|
||||||
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.11",
|
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.18",
|
||||||
"@tailwindcss/oxide-win32-x64-msvc": "4.1.11"
|
"@tailwindcss/oxide-win32-x64-msvc": "4.1.18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
|
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
||||||
"version": "4.1.11",
|
"version": "4.1.18",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz",
|
||||||
"integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==",
|
"integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10"
|
"node": ">= 10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide/node_modules/detect-libc": {
|
|
||||||
"version": "2.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
|
||||||
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/braces": {
|
|
||||||
"version": "3.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
|
||||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"fill-range": "^7.1.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/chownr": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
|
|
||||||
"license": "BlueOak-1.0.0",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/detect-libc": {
|
"node_modules/detect-libc": {
|
||||||
"version": "1.0.3",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||||
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
|
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
|
||||||
"detect-libc": "bin/detect-libc.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/enhanced-resolve": {
|
"node_modules/enhanced-resolve": {
|
||||||
"version": "5.18.2",
|
"version": "5.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz",
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz",
|
||||||
"integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==",
|
"integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"graceful-fs": "^4.2.4",
|
"graceful-fs": "^4.2.4",
|
||||||
"tapable": "^2.2.0"
|
"tapable": "^2.3.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.13.0"
|
"node": ">=10.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fill-range": {
|
|
||||||
"version": "7.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
|
||||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"to-regex-range": "^5.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/graceful-fs": {
|
"node_modules/graceful-fs": {
|
||||||
"version": "4.2.11",
|
"version": "4.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||||
|
|
@ -289,28 +224,19 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/is-number": {
|
|
||||||
"version": "7.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
|
||||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.12.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/jiti": {
|
"node_modules/jiti": {
|
||||||
"version": "2.5.1",
|
"version": "2.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
|
||||||
"integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==",
|
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"jiti": "lib/jiti-cli.mjs"
|
"jiti": "lib/jiti-cli.mjs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss": {
|
"node_modules/lightningcss": {
|
||||||
"version": "1.30.1",
|
"version": "1.30.2",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
|
||||||
"integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
|
"integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"detect-libc": "^2.0.3"
|
"detect-libc": "^2.0.3"
|
||||||
|
|
@ -323,29 +249,30 @@
|
||||||
"url": "https://opencollective.com/parcel"
|
"url": "https://opencollective.com/parcel"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"lightningcss-darwin-arm64": "1.30.1",
|
"lightningcss-android-arm64": "1.30.2",
|
||||||
"lightningcss-darwin-x64": "1.30.1",
|
"lightningcss-darwin-arm64": "1.30.2",
|
||||||
"lightningcss-freebsd-x64": "1.30.1",
|
"lightningcss-darwin-x64": "1.30.2",
|
||||||
"lightningcss-linux-arm-gnueabihf": "1.30.1",
|
"lightningcss-freebsd-x64": "1.30.2",
|
||||||
"lightningcss-linux-arm64-gnu": "1.30.1",
|
"lightningcss-linux-arm-gnueabihf": "1.30.2",
|
||||||
"lightningcss-linux-arm64-musl": "1.30.1",
|
"lightningcss-linux-arm64-gnu": "1.30.2",
|
||||||
"lightningcss-linux-x64-gnu": "1.30.1",
|
"lightningcss-linux-arm64-musl": "1.30.2",
|
||||||
"lightningcss-linux-x64-musl": "1.30.1",
|
"lightningcss-linux-x64-gnu": "1.30.2",
|
||||||
"lightningcss-win32-arm64-msvc": "1.30.1",
|
"lightningcss-linux-x64-musl": "1.30.2",
|
||||||
"lightningcss-win32-x64-msvc": "1.30.1"
|
"lightningcss-win32-arm64-msvc": "1.30.2",
|
||||||
|
"lightningcss-win32-x64-msvc": "1.30.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss-win32-x64-msvc": {
|
"node_modules/lightningcss-linux-x64-gnu": {
|
||||||
"version": "1.30.1",
|
"version": "1.30.2",
|
||||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz",
|
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
|
||||||
"integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==",
|
"integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"linux"
|
||||||
],
|
],
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
|
|
@ -355,71 +282,13 @@
|
||||||
"url": "https://opencollective.com/parcel"
|
"url": "https://opencollective.com/parcel"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lightningcss/node_modules/detect-libc": {
|
|
||||||
"version": "2.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
|
||||||
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.17",
|
"version": "0.30.21",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||||
"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
|
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.5.0"
|
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/micromatch": {
|
|
||||||
"version": "4.0.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
|
||||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"braces": "^3.0.3",
|
|
||||||
"picomatch": "^2.3.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/minipass": {
|
|
||||||
"version": "7.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
|
||||||
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
|
||||||
"license": "ISC",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16 || 14 >=14.17"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/minizlib": {
|
|
||||||
"version": "3.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz",
|
|
||||||
"integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"minipass": "^7.1.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/mkdirp": {
|
|
||||||
"version": "3.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
|
|
||||||
"integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"bin": {
|
|
||||||
"mkdirp": "dist/cjs/src/bin.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mri": {
|
"node_modules/mri": {
|
||||||
|
|
@ -444,12 +313,12 @@
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
"version": "2.3.1",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.6"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
|
|
@ -465,56 +334,22 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "4.1.11",
|
"version": "4.1.18",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
|
||||||
"integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==",
|
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/tapable": {
|
"node_modules/tapable": {
|
||||||
"version": "2.2.2",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
|
||||||
"integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==",
|
"integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/tar": {
|
"funding": {
|
||||||
"version": "7.4.3",
|
"type": "opencollective",
|
||||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
|
"url": "https://opencollective.com/webpack"
|
||||||
"integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
|
||||||
"@isaacs/fs-minipass": "^4.0.0",
|
|
||||||
"chownr": "^3.0.0",
|
|
||||||
"minipass": "^7.1.2",
|
|
||||||
"minizlib": "^3.0.1",
|
|
||||||
"mkdirp": "^3.0.1",
|
|
||||||
"yallist": "^5.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/to-regex-range": {
|
|
||||||
"version": "5.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
|
||||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"is-number": "^7.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/yallist": {
|
|
||||||
"version": "5.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
|
|
||||||
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
|
|
||||||
"license": "BlueOak-1.0.0",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,202 +0,0 @@
|
||||||
|
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
@ -1,218 +0,0 @@
|
||||||
# @ampproject/remapping
|
|
||||||
|
|
||||||
> Remap sequential sourcemaps through transformations to point at the original source code
|
|
||||||
|
|
||||||
Remapping allows you to take the sourcemaps generated through transforming your code and "remap"
|
|
||||||
them to the original source locations. Think "my minified code, transformed with babel and bundled
|
|
||||||
with webpack", all pointing to the correct location in your original source code.
|
|
||||||
|
|
||||||
With remapping, none of your source code transformations need to be aware of the input's sourcemap,
|
|
||||||
they only need to generate an output sourcemap. This greatly simplifies building custom
|
|
||||||
transformations (think a find-and-replace).
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm install @ampproject/remapping
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
function remapping(
|
|
||||||
map: SourceMap | SourceMap[],
|
|
||||||
loader: (file: string, ctx: LoaderContext) => (SourceMap | null | undefined),
|
|
||||||
options?: { excludeContent: boolean, decodedMappings: boolean }
|
|
||||||
): SourceMap;
|
|
||||||
|
|
||||||
// LoaderContext gives the loader the importing sourcemap, tree depth, the ability to override the
|
|
||||||
// "source" location (where child sources are resolved relative to, or the location of original
|
|
||||||
// source), and the ability to override the "content" of an original source for inclusion in the
|
|
||||||
// output sourcemap.
|
|
||||||
type LoaderContext = {
|
|
||||||
readonly importer: string;
|
|
||||||
readonly depth: number;
|
|
||||||
source: string;
|
|
||||||
content: string | null | undefined;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`remapping` takes the final output sourcemap, and a `loader` function. For every source file pointer
|
|
||||||
in the sourcemap, the `loader` will be called with the resolved path. If the path itself represents
|
|
||||||
a transformed file (it has a sourcmap associated with it), then the `loader` should return that
|
|
||||||
sourcemap. If not, the path will be treated as an original, untransformed source code.
|
|
||||||
|
|
||||||
```js
|
|
||||||
// Babel transformed "helloworld.js" into "transformed.js"
|
|
||||||
const transformedMap = JSON.stringify({
|
|
||||||
file: 'transformed.js',
|
|
||||||
// 1st column of 2nd line of output file translates into the 1st source
|
|
||||||
// file, line 3, column 2
|
|
||||||
mappings: ';CAEE',
|
|
||||||
sources: ['helloworld.js'],
|
|
||||||
version: 3,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Uglify minified "transformed.js" into "transformed.min.js"
|
|
||||||
const minifiedTransformedMap = JSON.stringify({
|
|
||||||
file: 'transformed.min.js',
|
|
||||||
// 0th column of 1st line of output file translates into the 1st source
|
|
||||||
// file, line 2, column 1.
|
|
||||||
mappings: 'AACC',
|
|
||||||
names: [],
|
|
||||||
sources: ['transformed.js'],
|
|
||||||
version: 3,
|
|
||||||
});
|
|
||||||
|
|
||||||
const remapped = remapping(
|
|
||||||
minifiedTransformedMap,
|
|
||||||
(file, ctx) => {
|
|
||||||
|
|
||||||
// The "transformed.js" file is an transformed file.
|
|
||||||
if (file === 'transformed.js') {
|
|
||||||
// The root importer is empty.
|
|
||||||
console.assert(ctx.importer === '');
|
|
||||||
// The depth in the sourcemap tree we're currently loading.
|
|
||||||
// The root `minifiedTransformedMap` is depth 0, and its source children are depth 1, etc.
|
|
||||||
console.assert(ctx.depth === 1);
|
|
||||||
|
|
||||||
return transformedMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loader will be called to load transformedMap's source file pointers as well.
|
|
||||||
console.assert(file === 'helloworld.js');
|
|
||||||
// `transformed.js`'s sourcemap points into `helloworld.js`.
|
|
||||||
console.assert(ctx.importer === 'transformed.js');
|
|
||||||
// This is a source child of `transformed`, which is a source child of `minifiedTransformedMap`.
|
|
||||||
console.assert(ctx.depth === 2);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(remapped);
|
|
||||||
// {
|
|
||||||
// file: 'transpiled.min.js',
|
|
||||||
// mappings: 'AAEE',
|
|
||||||
// sources: ['helloworld.js'],
|
|
||||||
// version: 3,
|
|
||||||
// };
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example, `loader` will be called twice:
|
|
||||||
|
|
||||||
1. `"transformed.js"`, the first source file pointer in the `minifiedTransformedMap`. We return the
|
|
||||||
associated sourcemap for it (its a transformed file, after all) so that sourcemap locations can
|
|
||||||
be traced through it into the source files it represents.
|
|
||||||
2. `"helloworld.js"`, our original, unmodified source code. This file does not have a sourcemap, so
|
|
||||||
we return `null`.
|
|
||||||
|
|
||||||
The `remapped` sourcemap now points from `transformed.min.js` into locations in `helloworld.js`. If
|
|
||||||
you were to read the `mappings`, it says "0th column of the first line output line points to the 1st
|
|
||||||
column of the 2nd line of the file `helloworld.js`".
|
|
||||||
|
|
||||||
### Multiple transformations of a file
|
|
||||||
|
|
||||||
As a convenience, if you have multiple single-source transformations of a file, you may pass an
|
|
||||||
array of sourcemap files in the order of most-recent transformation sourcemap first. Note that this
|
|
||||||
changes the `importer` and `depth` of each call to our loader. So our above example could have been
|
|
||||||
written as:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const remapped = remapping(
|
|
||||||
[minifiedTransformedMap, transformedMap],
|
|
||||||
() => null
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(remapped);
|
|
||||||
// {
|
|
||||||
// file: 'transpiled.min.js',
|
|
||||||
// mappings: 'AAEE',
|
|
||||||
// sources: ['helloworld.js'],
|
|
||||||
// version: 3,
|
|
||||||
// };
|
|
||||||
```
|
|
||||||
|
|
||||||
### Advanced control of the loading graph
|
|
||||||
|
|
||||||
#### `source`
|
|
||||||
|
|
||||||
The `source` property can overridden to any value to change the location of the current load. Eg,
|
|
||||||
for an original source file, it allows us to change the location to the original source regardless
|
|
||||||
of what the sourcemap source entry says. And for transformed files, it allows us to change the
|
|
||||||
relative resolving location for child sources of the loaded sourcemap.
|
|
||||||
|
|
||||||
```js
|
|
||||||
const remapped = remapping(
|
|
||||||
minifiedTransformedMap,
|
|
||||||
(file, ctx) => {
|
|
||||||
|
|
||||||
if (file === 'transformed.js') {
|
|
||||||
// We pretend the transformed.js file actually exists in the 'src/' directory. When the nested
|
|
||||||
// source files are loaded, they will now be relative to `src/`.
|
|
||||||
ctx.source = 'src/transformed.js';
|
|
||||||
return transformedMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.assert(file === 'src/helloworld.js');
|
|
||||||
// We could futher change the source of this original file, eg, to be inside a nested directory
|
|
||||||
// itself. This will be reflected in the remapped sourcemap.
|
|
||||||
ctx.source = 'src/nested/transformed.js';
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(remapped);
|
|
||||||
// {
|
|
||||||
// …,
|
|
||||||
// sources: ['src/nested/helloworld.js'],
|
|
||||||
// };
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
#### `content`
|
|
||||||
|
|
||||||
The `content` property can be overridden when we encounter an original source file. Eg, this allows
|
|
||||||
you to manually provide the source content of the original file regardless of whether the
|
|
||||||
`sourcesContent` field is present in the parent sourcemap. It can also be set to `null` to remove
|
|
||||||
the source content.
|
|
||||||
|
|
||||||
```js
|
|
||||||
const remapped = remapping(
|
|
||||||
minifiedTransformedMap,
|
|
||||||
(file, ctx) => {
|
|
||||||
|
|
||||||
if (file === 'transformed.js') {
|
|
||||||
// transformedMap does not include a `sourcesContent` field, so usually the remapped sourcemap
|
|
||||||
// would not include any `sourcesContent` values.
|
|
||||||
return transformedMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.assert(file === 'helloworld.js');
|
|
||||||
// We can read the file to provide the source content.
|
|
||||||
ctx.content = fs.readFileSync(file, 'utf8');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(remapped);
|
|
||||||
// {
|
|
||||||
// …,
|
|
||||||
// sourcesContent: [
|
|
||||||
// 'console.log("Hello world!")',
|
|
||||||
// ],
|
|
||||||
// };
|
|
||||||
```
|
|
||||||
|
|
||||||
### Options
|
|
||||||
|
|
||||||
#### excludeContent
|
|
||||||
|
|
||||||
By default, `excludeContent` is `false`. Passing `{ excludeContent: true }` will exclude the
|
|
||||||
`sourcesContent` field from the returned sourcemap. This is mainly useful when you want to reduce
|
|
||||||
the size out the sourcemap.
|
|
||||||
|
|
||||||
#### decodedMappings
|
|
||||||
|
|
||||||
By default, `decodedMappings` is `false`. Passing `{ decodedMappings: true }` will leave the
|
|
||||||
`mappings` field in a [decoded state](https://github.com/rich-harris/sourcemap-codec) instead of
|
|
||||||
encoding into a VLQ string.
|
|
||||||
|
|
@ -1,197 +0,0 @@
|
||||||
import { decodedMappings, traceSegment, TraceMap } from '@jridgewell/trace-mapping';
|
|
||||||
import { GenMapping, maybeAddSegment, setSourceContent, setIgnore, toDecodedMap, toEncodedMap } from '@jridgewell/gen-mapping';
|
|
||||||
|
|
||||||
const SOURCELESS_MAPPING = /* #__PURE__ */ SegmentObject('', -1, -1, '', null, false);
|
|
||||||
const EMPTY_SOURCES = [];
|
|
||||||
function SegmentObject(source, line, column, name, content, ignore) {
|
|
||||||
return { source, line, column, name, content, ignore };
|
|
||||||
}
|
|
||||||
function Source(map, sources, source, content, ignore) {
|
|
||||||
return {
|
|
||||||
map,
|
|
||||||
sources,
|
|
||||||
source,
|
|
||||||
content,
|
|
||||||
ignore,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* MapSource represents a single sourcemap, with the ability to trace mappings into its child nodes
|
|
||||||
* (which may themselves be SourceMapTrees).
|
|
||||||
*/
|
|
||||||
function MapSource(map, sources) {
|
|
||||||
return Source(map, sources, '', null, false);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* A "leaf" node in the sourcemap tree, representing an original, unmodified source file. Recursive
|
|
||||||
* segment tracing ends at the `OriginalSource`.
|
|
||||||
*/
|
|
||||||
function OriginalSource(source, content, ignore) {
|
|
||||||
return Source(null, EMPTY_SOURCES, source, content, ignore);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* traceMappings is only called on the root level SourceMapTree, and begins the process of
|
|
||||||
* resolving each mapping in terms of the original source files.
|
|
||||||
*/
|
|
||||||
function traceMappings(tree) {
|
|
||||||
// TODO: Eventually support sourceRoot, which has to be removed because the sources are already
|
|
||||||
// fully resolved. We'll need to make sources relative to the sourceRoot before adding them.
|
|
||||||
const gen = new GenMapping({ file: tree.map.file });
|
|
||||||
const { sources: rootSources, map } = tree;
|
|
||||||
const rootNames = map.names;
|
|
||||||
const rootMappings = decodedMappings(map);
|
|
||||||
for (let i = 0; i < rootMappings.length; i++) {
|
|
||||||
const segments = rootMappings[i];
|
|
||||||
for (let j = 0; j < segments.length; j++) {
|
|
||||||
const segment = segments[j];
|
|
||||||
const genCol = segment[0];
|
|
||||||
let traced = SOURCELESS_MAPPING;
|
|
||||||
// 1-length segments only move the current generated column, there's no source information
|
|
||||||
// to gather from it.
|
|
||||||
if (segment.length !== 1) {
|
|
||||||
const source = rootSources[segment[1]];
|
|
||||||
traced = originalPositionFor(source, segment[2], segment[3], segment.length === 5 ? rootNames[segment[4]] : '');
|
|
||||||
// If the trace is invalid, then the trace ran into a sourcemap that doesn't contain a
|
|
||||||
// respective segment into an original source.
|
|
||||||
if (traced == null)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const { column, line, name, content, source, ignore } = traced;
|
|
||||||
maybeAddSegment(gen, i, genCol, source, line, column, name);
|
|
||||||
if (source && content != null)
|
|
||||||
setSourceContent(gen, source, content);
|
|
||||||
if (ignore)
|
|
||||||
setIgnore(gen, source, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return gen;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* originalPositionFor is only called on children SourceMapTrees. It recurses down into its own
|
|
||||||
* child SourceMapTrees, until we find the original source map.
|
|
||||||
*/
|
|
||||||
function originalPositionFor(source, line, column, name) {
|
|
||||||
if (!source.map) {
|
|
||||||
return SegmentObject(source.source, line, column, name, source.content, source.ignore);
|
|
||||||
}
|
|
||||||
const segment = traceSegment(source.map, line, column);
|
|
||||||
// If we couldn't find a segment, then this doesn't exist in the sourcemap.
|
|
||||||
if (segment == null)
|
|
||||||
return null;
|
|
||||||
// 1-length segments only move the current generated column, there's no source information
|
|
||||||
// to gather from it.
|
|
||||||
if (segment.length === 1)
|
|
||||||
return SOURCELESS_MAPPING;
|
|
||||||
return originalPositionFor(source.sources[segment[1]], segment[2], segment[3], segment.length === 5 ? source.map.names[segment[4]] : name);
|
|
||||||
}
|
|
||||||
|
|
||||||
function asArray(value) {
|
|
||||||
if (Array.isArray(value))
|
|
||||||
return value;
|
|
||||||
return [value];
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Recursively builds a tree structure out of sourcemap files, with each node
|
|
||||||
* being either an `OriginalSource` "leaf" or a `SourceMapTree` composed of
|
|
||||||
* `OriginalSource`s and `SourceMapTree`s.
|
|
||||||
*
|
|
||||||
* Every sourcemap is composed of a collection of source files and mappings
|
|
||||||
* into locations of those source files. When we generate a `SourceMapTree` for
|
|
||||||
* the sourcemap, we attempt to load each source file's own sourcemap. If it
|
|
||||||
* does not have an associated sourcemap, it is considered an original,
|
|
||||||
* unmodified source file.
|
|
||||||
*/
|
|
||||||
function buildSourceMapTree(input, loader) {
|
|
||||||
const maps = asArray(input).map((m) => new TraceMap(m, ''));
|
|
||||||
const map = maps.pop();
|
|
||||||
for (let i = 0; i < maps.length; i++) {
|
|
||||||
if (maps[i].sources.length > 1) {
|
|
||||||
throw new Error(`Transformation map ${i} must have exactly one source file.\n` +
|
|
||||||
'Did you specify these with the most recent transformation maps first?');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let tree = build(map, loader, '', 0);
|
|
||||||
for (let i = maps.length - 1; i >= 0; i--) {
|
|
||||||
tree = MapSource(maps[i], [tree]);
|
|
||||||
}
|
|
||||||
return tree;
|
|
||||||
}
|
|
||||||
function build(map, loader, importer, importerDepth) {
|
|
||||||
const { resolvedSources, sourcesContent, ignoreList } = map;
|
|
||||||
const depth = importerDepth + 1;
|
|
||||||
const children = resolvedSources.map((sourceFile, i) => {
|
|
||||||
// The loading context gives the loader more information about why this file is being loaded
|
|
||||||
// (eg, from which importer). It also allows the loader to override the location of the loaded
|
|
||||||
// sourcemap/original source, or to override the content in the sourcesContent field if it's
|
|
||||||
// an unmodified source file.
|
|
||||||
const ctx = {
|
|
||||||
importer,
|
|
||||||
depth,
|
|
||||||
source: sourceFile || '',
|
|
||||||
content: undefined,
|
|
||||||
ignore: undefined,
|
|
||||||
};
|
|
||||||
// Use the provided loader callback to retrieve the file's sourcemap.
|
|
||||||
// TODO: We should eventually support async loading of sourcemap files.
|
|
||||||
const sourceMap = loader(ctx.source, ctx);
|
|
||||||
const { source, content, ignore } = ctx;
|
|
||||||
// If there is a sourcemap, then we need to recurse into it to load its source files.
|
|
||||||
if (sourceMap)
|
|
||||||
return build(new TraceMap(sourceMap, source), loader, source, depth);
|
|
||||||
// Else, it's an unmodified source file.
|
|
||||||
// The contents of this unmodified source file can be overridden via the loader context,
|
|
||||||
// allowing it to be explicitly null or a string. If it remains undefined, we fall back to
|
|
||||||
// the importing sourcemap's `sourcesContent` field.
|
|
||||||
const sourceContent = content !== undefined ? content : sourcesContent ? sourcesContent[i] : null;
|
|
||||||
const ignored = ignore !== undefined ? ignore : ignoreList ? ignoreList.includes(i) : false;
|
|
||||||
return OriginalSource(source, sourceContent, ignored);
|
|
||||||
});
|
|
||||||
return MapSource(map, children);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A SourceMap v3 compatible sourcemap, which only includes fields that were
|
|
||||||
* provided to it.
|
|
||||||
*/
|
|
||||||
class SourceMap {
|
|
||||||
constructor(map, options) {
|
|
||||||
const out = options.decodedMappings ? toDecodedMap(map) : toEncodedMap(map);
|
|
||||||
this.version = out.version; // SourceMap spec says this should be first.
|
|
||||||
this.file = out.file;
|
|
||||||
this.mappings = out.mappings;
|
|
||||||
this.names = out.names;
|
|
||||||
this.ignoreList = out.ignoreList;
|
|
||||||
this.sourceRoot = out.sourceRoot;
|
|
||||||
this.sources = out.sources;
|
|
||||||
if (!options.excludeContent) {
|
|
||||||
this.sourcesContent = out.sourcesContent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
toString() {
|
|
||||||
return JSON.stringify(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Traces through all the mappings in the root sourcemap, through the sources
|
|
||||||
* (and their sourcemaps), all the way back to the original source location.
|
|
||||||
*
|
|
||||||
* `loader` will be called every time we encounter a source file. If it returns
|
|
||||||
* a sourcemap, we will recurse into that sourcemap to continue the trace. If
|
|
||||||
* it returns a falsey value, that source file is treated as an original,
|
|
||||||
* unmodified source file.
|
|
||||||
*
|
|
||||||
* Pass `excludeContent` to exclude any self-containing source file content
|
|
||||||
* from the output sourcemap.
|
|
||||||
*
|
|
||||||
* Pass `decodedMappings` to receive a SourceMap with decoded (instead of
|
|
||||||
* VLQ encoded) mappings.
|
|
||||||
*/
|
|
||||||
function remapping(input, loader, options) {
|
|
||||||
const opts = typeof options === 'object' ? options : { excludeContent: !!options, decodedMappings: false };
|
|
||||||
const tree = buildSourceMapTree(input, loader);
|
|
||||||
return new SourceMap(traceMappings(tree), opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { remapping as default };
|
|
||||||
//# sourceMappingURL=remapping.mjs.map
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,202 +0,0 @@
|
||||||
(function (global, factory) {
|
|
||||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('@jridgewell/trace-mapping'), require('@jridgewell/gen-mapping')) :
|
|
||||||
typeof define === 'function' && define.amd ? define(['@jridgewell/trace-mapping', '@jridgewell/gen-mapping'], factory) :
|
|
||||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.remapping = factory(global.traceMapping, global.genMapping));
|
|
||||||
})(this, (function (traceMapping, genMapping) { 'use strict';
|
|
||||||
|
|
||||||
const SOURCELESS_MAPPING = /* #__PURE__ */ SegmentObject('', -1, -1, '', null, false);
|
|
||||||
const EMPTY_SOURCES = [];
|
|
||||||
function SegmentObject(source, line, column, name, content, ignore) {
|
|
||||||
return { source, line, column, name, content, ignore };
|
|
||||||
}
|
|
||||||
function Source(map, sources, source, content, ignore) {
|
|
||||||
return {
|
|
||||||
map,
|
|
||||||
sources,
|
|
||||||
source,
|
|
||||||
content,
|
|
||||||
ignore,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* MapSource represents a single sourcemap, with the ability to trace mappings into its child nodes
|
|
||||||
* (which may themselves be SourceMapTrees).
|
|
||||||
*/
|
|
||||||
function MapSource(map, sources) {
|
|
||||||
return Source(map, sources, '', null, false);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* A "leaf" node in the sourcemap tree, representing an original, unmodified source file. Recursive
|
|
||||||
* segment tracing ends at the `OriginalSource`.
|
|
||||||
*/
|
|
||||||
function OriginalSource(source, content, ignore) {
|
|
||||||
return Source(null, EMPTY_SOURCES, source, content, ignore);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* traceMappings is only called on the root level SourceMapTree, and begins the process of
|
|
||||||
* resolving each mapping in terms of the original source files.
|
|
||||||
*/
|
|
||||||
function traceMappings(tree) {
|
|
||||||
// TODO: Eventually support sourceRoot, which has to be removed because the sources are already
|
|
||||||
// fully resolved. We'll need to make sources relative to the sourceRoot before adding them.
|
|
||||||
const gen = new genMapping.GenMapping({ file: tree.map.file });
|
|
||||||
const { sources: rootSources, map } = tree;
|
|
||||||
const rootNames = map.names;
|
|
||||||
const rootMappings = traceMapping.decodedMappings(map);
|
|
||||||
for (let i = 0; i < rootMappings.length; i++) {
|
|
||||||
const segments = rootMappings[i];
|
|
||||||
for (let j = 0; j < segments.length; j++) {
|
|
||||||
const segment = segments[j];
|
|
||||||
const genCol = segment[0];
|
|
||||||
let traced = SOURCELESS_MAPPING;
|
|
||||||
// 1-length segments only move the current generated column, there's no source information
|
|
||||||
// to gather from it.
|
|
||||||
if (segment.length !== 1) {
|
|
||||||
const source = rootSources[segment[1]];
|
|
||||||
traced = originalPositionFor(source, segment[2], segment[3], segment.length === 5 ? rootNames[segment[4]] : '');
|
|
||||||
// If the trace is invalid, then the trace ran into a sourcemap that doesn't contain a
|
|
||||||
// respective segment into an original source.
|
|
||||||
if (traced == null)
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const { column, line, name, content, source, ignore } = traced;
|
|
||||||
genMapping.maybeAddSegment(gen, i, genCol, source, line, column, name);
|
|
||||||
if (source && content != null)
|
|
||||||
genMapping.setSourceContent(gen, source, content);
|
|
||||||
if (ignore)
|
|
||||||
genMapping.setIgnore(gen, source, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return gen;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* originalPositionFor is only called on children SourceMapTrees. It recurses down into its own
|
|
||||||
* child SourceMapTrees, until we find the original source map.
|
|
||||||
*/
|
|
||||||
function originalPositionFor(source, line, column, name) {
|
|
||||||
if (!source.map) {
|
|
||||||
return SegmentObject(source.source, line, column, name, source.content, source.ignore);
|
|
||||||
}
|
|
||||||
const segment = traceMapping.traceSegment(source.map, line, column);
|
|
||||||
// If we couldn't find a segment, then this doesn't exist in the sourcemap.
|
|
||||||
if (segment == null)
|
|
||||||
return null;
|
|
||||||
// 1-length segments only move the current generated column, there's no source information
|
|
||||||
// to gather from it.
|
|
||||||
if (segment.length === 1)
|
|
||||||
return SOURCELESS_MAPPING;
|
|
||||||
return originalPositionFor(source.sources[segment[1]], segment[2], segment[3], segment.length === 5 ? source.map.names[segment[4]] : name);
|
|
||||||
}
|
|
||||||
|
|
||||||
function asArray(value) {
|
|
||||||
if (Array.isArray(value))
|
|
||||||
return value;
|
|
||||||
return [value];
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Recursively builds a tree structure out of sourcemap files, with each node
|
|
||||||
* being either an `OriginalSource` "leaf" or a `SourceMapTree` composed of
|
|
||||||
* `OriginalSource`s and `SourceMapTree`s.
|
|
||||||
*
|
|
||||||
* Every sourcemap is composed of a collection of source files and mappings
|
|
||||||
* into locations of those source files. When we generate a `SourceMapTree` for
|
|
||||||
* the sourcemap, we attempt to load each source file's own sourcemap. If it
|
|
||||||
* does not have an associated sourcemap, it is considered an original,
|
|
||||||
* unmodified source file.
|
|
||||||
*/
|
|
||||||
function buildSourceMapTree(input, loader) {
|
|
||||||
const maps = asArray(input).map((m) => new traceMapping.TraceMap(m, ''));
|
|
||||||
const map = maps.pop();
|
|
||||||
for (let i = 0; i < maps.length; i++) {
|
|
||||||
if (maps[i].sources.length > 1) {
|
|
||||||
throw new Error(`Transformation map ${i} must have exactly one source file.\n` +
|
|
||||||
'Did you specify these with the most recent transformation maps first?');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let tree = build(map, loader, '', 0);
|
|
||||||
for (let i = maps.length - 1; i >= 0; i--) {
|
|
||||||
tree = MapSource(maps[i], [tree]);
|
|
||||||
}
|
|
||||||
return tree;
|
|
||||||
}
|
|
||||||
function build(map, loader, importer, importerDepth) {
|
|
||||||
const { resolvedSources, sourcesContent, ignoreList } = map;
|
|
||||||
const depth = importerDepth + 1;
|
|
||||||
const children = resolvedSources.map((sourceFile, i) => {
|
|
||||||
// The loading context gives the loader more information about why this file is being loaded
|
|
||||||
// (eg, from which importer). It also allows the loader to override the location of the loaded
|
|
||||||
// sourcemap/original source, or to override the content in the sourcesContent field if it's
|
|
||||||
// an unmodified source file.
|
|
||||||
const ctx = {
|
|
||||||
importer,
|
|
||||||
depth,
|
|
||||||
source: sourceFile || '',
|
|
||||||
content: undefined,
|
|
||||||
ignore: undefined,
|
|
||||||
};
|
|
||||||
// Use the provided loader callback to retrieve the file's sourcemap.
|
|
||||||
// TODO: We should eventually support async loading of sourcemap files.
|
|
||||||
const sourceMap = loader(ctx.source, ctx);
|
|
||||||
const { source, content, ignore } = ctx;
|
|
||||||
// If there is a sourcemap, then we need to recurse into it to load its source files.
|
|
||||||
if (sourceMap)
|
|
||||||
return build(new traceMapping.TraceMap(sourceMap, source), loader, source, depth);
|
|
||||||
// Else, it's an unmodified source file.
|
|
||||||
// The contents of this unmodified source file can be overridden via the loader context,
|
|
||||||
// allowing it to be explicitly null or a string. If it remains undefined, we fall back to
|
|
||||||
// the importing sourcemap's `sourcesContent` field.
|
|
||||||
const sourceContent = content !== undefined ? content : sourcesContent ? sourcesContent[i] : null;
|
|
||||||
const ignored = ignore !== undefined ? ignore : ignoreList ? ignoreList.includes(i) : false;
|
|
||||||
return OriginalSource(source, sourceContent, ignored);
|
|
||||||
});
|
|
||||||
return MapSource(map, children);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A SourceMap v3 compatible sourcemap, which only includes fields that were
|
|
||||||
* provided to it.
|
|
||||||
*/
|
|
||||||
class SourceMap {
|
|
||||||
constructor(map, options) {
|
|
||||||
const out = options.decodedMappings ? genMapping.toDecodedMap(map) : genMapping.toEncodedMap(map);
|
|
||||||
this.version = out.version; // SourceMap spec says this should be first.
|
|
||||||
this.file = out.file;
|
|
||||||
this.mappings = out.mappings;
|
|
||||||
this.names = out.names;
|
|
||||||
this.ignoreList = out.ignoreList;
|
|
||||||
this.sourceRoot = out.sourceRoot;
|
|
||||||
this.sources = out.sources;
|
|
||||||
if (!options.excludeContent) {
|
|
||||||
this.sourcesContent = out.sourcesContent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
toString() {
|
|
||||||
return JSON.stringify(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Traces through all the mappings in the root sourcemap, through the sources
|
|
||||||
* (and their sourcemaps), all the way back to the original source location.
|
|
||||||
*
|
|
||||||
* `loader` will be called every time we encounter a source file. If it returns
|
|
||||||
* a sourcemap, we will recurse into that sourcemap to continue the trace. If
|
|
||||||
* it returns a falsey value, that source file is treated as an original,
|
|
||||||
* unmodified source file.
|
|
||||||
*
|
|
||||||
* Pass `excludeContent` to exclude any self-containing source file content
|
|
||||||
* from the output sourcemap.
|
|
||||||
*
|
|
||||||
* Pass `decodedMappings` to receive a SourceMap with decoded (instead of
|
|
||||||
* VLQ encoded) mappings.
|
|
||||||
*/
|
|
||||||
function remapping(input, loader, options) {
|
|
||||||
const opts = typeof options === 'object' ? options : { excludeContent: !!options, decodedMappings: false };
|
|
||||||
const tree = buildSourceMapTree(input, loader);
|
|
||||||
return new SourceMap(traceMappings(tree), opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
return remapping;
|
|
||||||
|
|
||||||
}));
|
|
||||||
//# sourceMappingURL=remapping.umd.js.map
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,14 +0,0 @@
|
||||||
import type { MapSource as MapSourceType } from './source-map-tree';
|
|
||||||
import type { SourceMapInput, SourceMapLoader } from './types';
|
|
||||||
/**
|
|
||||||
* Recursively builds a tree structure out of sourcemap files, with each node
|
|
||||||
* being either an `OriginalSource` "leaf" or a `SourceMapTree` composed of
|
|
||||||
* `OriginalSource`s and `SourceMapTree`s.
|
|
||||||
*
|
|
||||||
* Every sourcemap is composed of a collection of source files and mappings
|
|
||||||
* into locations of those source files. When we generate a `SourceMapTree` for
|
|
||||||
* the sourcemap, we attempt to load each source file's own sourcemap. If it
|
|
||||||
* does not have an associated sourcemap, it is considered an original,
|
|
||||||
* unmodified source file.
|
|
||||||
*/
|
|
||||||
export default function buildSourceMapTree(input: SourceMapInput | SourceMapInput[], loader: SourceMapLoader): MapSourceType;
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import SourceMap from './source-map';
|
|
||||||
import type { SourceMapInput, SourceMapLoader, Options } from './types';
|
|
||||||
export type { SourceMapSegment, EncodedSourceMap, EncodedSourceMap as RawSourceMap, DecodedSourceMap, SourceMapInput, SourceMapLoader, LoaderContext, Options, } from './types';
|
|
||||||
export type { SourceMap };
|
|
||||||
/**
|
|
||||||
* Traces through all the mappings in the root sourcemap, through the sources
|
|
||||||
* (and their sourcemaps), all the way back to the original source location.
|
|
||||||
*
|
|
||||||
* `loader` will be called every time we encounter a source file. If it returns
|
|
||||||
* a sourcemap, we will recurse into that sourcemap to continue the trace. If
|
|
||||||
* it returns a falsey value, that source file is treated as an original,
|
|
||||||
* unmodified source file.
|
|
||||||
*
|
|
||||||
* Pass `excludeContent` to exclude any self-containing source file content
|
|
||||||
* from the output sourcemap.
|
|
||||||
*
|
|
||||||
* Pass `decodedMappings` to receive a SourceMap with decoded (instead of
|
|
||||||
* VLQ encoded) mappings.
|
|
||||||
*/
|
|
||||||
export default function remapping(input: SourceMapInput | SourceMapInput[], loader: SourceMapLoader, options?: boolean | Options): SourceMap;
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
import { GenMapping } from '@jridgewell/gen-mapping';
|
|
||||||
import type { TraceMap } from '@jridgewell/trace-mapping';
|
|
||||||
export declare type SourceMapSegmentObject = {
|
|
||||||
column: number;
|
|
||||||
line: number;
|
|
||||||
name: string;
|
|
||||||
source: string;
|
|
||||||
content: string | null;
|
|
||||||
ignore: boolean;
|
|
||||||
};
|
|
||||||
export declare type OriginalSource = {
|
|
||||||
map: null;
|
|
||||||
sources: Sources[];
|
|
||||||
source: string;
|
|
||||||
content: string | null;
|
|
||||||
ignore: boolean;
|
|
||||||
};
|
|
||||||
export declare type MapSource = {
|
|
||||||
map: TraceMap;
|
|
||||||
sources: Sources[];
|
|
||||||
source: string;
|
|
||||||
content: null;
|
|
||||||
ignore: false;
|
|
||||||
};
|
|
||||||
export declare type Sources = OriginalSource | MapSource;
|
|
||||||
/**
|
|
||||||
* MapSource represents a single sourcemap, with the ability to trace mappings into its child nodes
|
|
||||||
* (which may themselves be SourceMapTrees).
|
|
||||||
*/
|
|
||||||
export declare function MapSource(map: TraceMap, sources: Sources[]): MapSource;
|
|
||||||
/**
|
|
||||||
* A "leaf" node in the sourcemap tree, representing an original, unmodified source file. Recursive
|
|
||||||
* segment tracing ends at the `OriginalSource`.
|
|
||||||
*/
|
|
||||||
export declare function OriginalSource(source: string, content: string | null, ignore: boolean): OriginalSource;
|
|
||||||
/**
|
|
||||||
* traceMappings is only called on the root level SourceMapTree, and begins the process of
|
|
||||||
* resolving each mapping in terms of the original source files.
|
|
||||||
*/
|
|
||||||
export declare function traceMappings(tree: MapSource): GenMapping;
|
|
||||||
/**
|
|
||||||
* originalPositionFor is only called on children SourceMapTrees. It recurses down into its own
|
|
||||||
* child SourceMapTrees, until we find the original source map.
|
|
||||||
*/
|
|
||||||
export declare function originalPositionFor(source: Sources, line: number, column: number, name: string): SourceMapSegmentObject | null;
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
import type { GenMapping } from '@jridgewell/gen-mapping';
|
|
||||||
import type { DecodedSourceMap, EncodedSourceMap, Options } from './types';
|
|
||||||
/**
|
|
||||||
* A SourceMap v3 compatible sourcemap, which only includes fields that were
|
|
||||||
* provided to it.
|
|
||||||
*/
|
|
||||||
export default class SourceMap {
|
|
||||||
file?: string | null;
|
|
||||||
mappings: EncodedSourceMap['mappings'] | DecodedSourceMap['mappings'];
|
|
||||||
sourceRoot?: string;
|
|
||||||
names: string[];
|
|
||||||
sources: (string | null)[];
|
|
||||||
sourcesContent?: (string | null)[];
|
|
||||||
version: 3;
|
|
||||||
ignoreList: number[] | undefined;
|
|
||||||
constructor(map: GenMapping, options: Options);
|
|
||||||
toString(): string;
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import type { SourceMapInput } from '@jridgewell/trace-mapping';
|
|
||||||
export type { SourceMapSegment, DecodedSourceMap, EncodedSourceMap, } from '@jridgewell/trace-mapping';
|
|
||||||
export type { SourceMapInput };
|
|
||||||
export declare type LoaderContext = {
|
|
||||||
readonly importer: string;
|
|
||||||
readonly depth: number;
|
|
||||||
source: string;
|
|
||||||
content: string | null | undefined;
|
|
||||||
ignore: boolean | undefined;
|
|
||||||
};
|
|
||||||
export declare type SourceMapLoader = (file: string, ctx: LoaderContext) => SourceMapInput | null | undefined | void;
|
|
||||||
export declare type Options = {
|
|
||||||
excludeContent?: boolean;
|
|
||||||
decodedMappings?: boolean;
|
|
||||||
};
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@ampproject/remapping",
|
|
||||||
"version": "2.3.0",
|
|
||||||
"description": "Remap sequential sourcemaps through transformations to point at the original source code",
|
|
||||||
"keywords": [
|
|
||||||
"source",
|
|
||||||
"map",
|
|
||||||
"remap"
|
|
||||||
],
|
|
||||||
"main": "dist/remapping.umd.js",
|
|
||||||
"module": "dist/remapping.mjs",
|
|
||||||
"types": "dist/types/remapping.d.ts",
|
|
||||||
"exports": {
|
|
||||||
".": [
|
|
||||||
{
|
|
||||||
"types": "./dist/types/remapping.d.ts",
|
|
||||||
"browser": "./dist/remapping.umd.js",
|
|
||||||
"require": "./dist/remapping.umd.js",
|
|
||||||
"import": "./dist/remapping.mjs"
|
|
||||||
},
|
|
||||||
"./dist/remapping.umd.js"
|
|
||||||
],
|
|
||||||
"./package.json": "./package.json"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"dist"
|
|
||||||
],
|
|
||||||
"author": "Justin Ridgewell <jridgewell@google.com>",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/ampproject/remapping.git"
|
|
||||||
},
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.0.0"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build": "run-s -n build:*",
|
|
||||||
"build:rollup": "rollup -c rollup.config.js",
|
|
||||||
"build:ts": "tsc --project tsconfig.build.json",
|
|
||||||
"lint": "run-s -n lint:*",
|
|
||||||
"lint:prettier": "npm run test:lint:prettier -- --write",
|
|
||||||
"lint:ts": "npm run test:lint:ts -- --fix",
|
|
||||||
"prebuild": "rm -rf dist",
|
|
||||||
"prepublishOnly": "npm run preversion",
|
|
||||||
"preversion": "run-s test build",
|
|
||||||
"test": "run-s -n test:lint test:only",
|
|
||||||
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand",
|
|
||||||
"test:lint": "run-s -n test:lint:*",
|
|
||||||
"test:lint:prettier": "prettier --check '{src,test}/**/*.ts'",
|
|
||||||
"test:lint:ts": "eslint '{src,test}/**/*.ts'",
|
|
||||||
"test:only": "jest --coverage",
|
|
||||||
"test:watch": "jest --coverage --watch"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@rollup/plugin-typescript": "8.3.2",
|
|
||||||
"@types/jest": "27.4.1",
|
|
||||||
"@typescript-eslint/eslint-plugin": "5.20.0",
|
|
||||||
"@typescript-eslint/parser": "5.20.0",
|
|
||||||
"eslint": "8.14.0",
|
|
||||||
"eslint-config-prettier": "8.5.0",
|
|
||||||
"jest": "27.5.1",
|
|
||||||
"jest-config": "27.5.1",
|
|
||||||
"npm-run-all": "4.1.5",
|
|
||||||
"prettier": "2.6.2",
|
|
||||||
"rollup": "2.70.2",
|
|
||||||
"ts-jest": "27.1.4",
|
|
||||||
"tslib": "2.4.0",
|
|
||||||
"typescript": "4.6.3"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@jridgewell/gen-mapping": "^0.3.5",
|
|
||||||
"@jridgewell/trace-mapping": "^0.3.24"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
The ISC License
|
|
||||||
|
|
||||||
Copyright (c) Isaac Z. Schlueter and Contributors
|
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
|
||||||
copyright notice and this permission notice appear in all copies.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
|
||||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
# fs-minipass
|
|
||||||
|
|
||||||
Filesystem streams based on [minipass](http://npm.im/minipass).
|
|
||||||
|
|
||||||
4 classes are exported:
|
|
||||||
|
|
||||||
- ReadStream
|
|
||||||
- ReadStreamSync
|
|
||||||
- WriteStream
|
|
||||||
- WriteStreamSync
|
|
||||||
|
|
||||||
When using `ReadStreamSync`, all of the data is made available
|
|
||||||
immediately upon consuming the stream. Nothing is buffered in memory
|
|
||||||
when the stream is constructed. If the stream is piped to a writer,
|
|
||||||
then it will synchronously `read()` and emit data into the writer as
|
|
||||||
fast as the writer can consume it. (That is, it will respect
|
|
||||||
backpressure.) If you call `stream.read()` then it will read the
|
|
||||||
entire file and return the contents.
|
|
||||||
|
|
||||||
When using `WriteStreamSync`, every write is flushed to the file
|
|
||||||
synchronously. If your writes all come in a single tick, then it'll
|
|
||||||
write it all out in a single tick. It's as synchronous as you are.
|
|
||||||
|
|
||||||
The async versions work much like their node builtin counterparts,
|
|
||||||
with the exception of introducing significantly less Stream machinery
|
|
||||||
overhead.
|
|
||||||
|
|
||||||
## USAGE
|
|
||||||
|
|
||||||
It's just streams, you pipe them or read() them or write() to them.
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { ReadStream, WriteStream } from 'fs-minipass'
|
|
||||||
// or: const { ReadStream, WriteStream } = require('fs-minipass')
|
|
||||||
const readStream = new ReadStream('file.txt')
|
|
||||||
const writeStream = new WriteStream('output.txt')
|
|
||||||
writeStream.write('some file header or whatever\n')
|
|
||||||
readStream.pipe(writeStream)
|
|
||||||
```
|
|
||||||
|
|
||||||
## ReadStream(path, options)
|
|
||||||
|
|
||||||
Path string is required, but somewhat irrelevant if an open file
|
|
||||||
descriptor is passed in as an option.
|
|
||||||
|
|
||||||
Options:
|
|
||||||
|
|
||||||
- `fd` Pass in a numeric file descriptor, if the file is already open.
|
|
||||||
- `readSize` The size of reads to do, defaults to 16MB
|
|
||||||
- `size` The size of the file, if known. Prevents zero-byte read()
|
|
||||||
call at the end.
|
|
||||||
- `autoClose` Set to `false` to prevent the file descriptor from being
|
|
||||||
closed when the file is done being read.
|
|
||||||
|
|
||||||
## WriteStream(path, options)
|
|
||||||
|
|
||||||
Path string is required, but somewhat irrelevant if an open file
|
|
||||||
descriptor is passed in as an option.
|
|
||||||
|
|
||||||
Options:
|
|
||||||
|
|
||||||
- `fd` Pass in a numeric file descriptor, if the file is already open.
|
|
||||||
- `mode` The mode to create the file with. Defaults to `0o666`.
|
|
||||||
- `start` The position in the file to start reading. If not
|
|
||||||
specified, then the file will start writing at position zero, and be
|
|
||||||
truncated by default.
|
|
||||||
- `autoClose` Set to `false` to prevent the file descriptor from being
|
|
||||||
closed when the stream is ended.
|
|
||||||
- `flags` Flags to use when opening the file. Irrelevant if `fd` is
|
|
||||||
passed in, since file won't be opened in that case. Defaults to
|
|
||||||
`'a'` if a `pos` is specified, or `'w'` otherwise.
|
|
||||||
|
|
@ -1,118 +0,0 @@
|
||||||
/// <reference types="node" />
|
|
||||||
/// <reference types="node" />
|
|
||||||
/// <reference types="node" />
|
|
||||||
import EE from 'events';
|
|
||||||
import { Minipass } from 'minipass';
|
|
||||||
declare const _autoClose: unique symbol;
|
|
||||||
declare const _close: unique symbol;
|
|
||||||
declare const _ended: unique symbol;
|
|
||||||
declare const _fd: unique symbol;
|
|
||||||
declare const _finished: unique symbol;
|
|
||||||
declare const _flags: unique symbol;
|
|
||||||
declare const _flush: unique symbol;
|
|
||||||
declare const _handleChunk: unique symbol;
|
|
||||||
declare const _makeBuf: unique symbol;
|
|
||||||
declare const _mode: unique symbol;
|
|
||||||
declare const _needDrain: unique symbol;
|
|
||||||
declare const _onerror: unique symbol;
|
|
||||||
declare const _onopen: unique symbol;
|
|
||||||
declare const _onread: unique symbol;
|
|
||||||
declare const _onwrite: unique symbol;
|
|
||||||
declare const _open: unique symbol;
|
|
||||||
declare const _path: unique symbol;
|
|
||||||
declare const _pos: unique symbol;
|
|
||||||
declare const _queue: unique symbol;
|
|
||||||
declare const _read: unique symbol;
|
|
||||||
declare const _readSize: unique symbol;
|
|
||||||
declare const _reading: unique symbol;
|
|
||||||
declare const _remain: unique symbol;
|
|
||||||
declare const _size: unique symbol;
|
|
||||||
declare const _write: unique symbol;
|
|
||||||
declare const _writing: unique symbol;
|
|
||||||
declare const _defaultFlag: unique symbol;
|
|
||||||
declare const _errored: unique symbol;
|
|
||||||
export type ReadStreamOptions = Minipass.Options<Minipass.ContiguousData> & {
|
|
||||||
fd?: number;
|
|
||||||
readSize?: number;
|
|
||||||
size?: number;
|
|
||||||
autoClose?: boolean;
|
|
||||||
};
|
|
||||||
export type ReadStreamEvents = Minipass.Events<Minipass.ContiguousData> & {
|
|
||||||
open: [fd: number];
|
|
||||||
};
|
|
||||||
export declare class ReadStream extends Minipass<Minipass.ContiguousData, Buffer, ReadStreamEvents> {
|
|
||||||
[_errored]: boolean;
|
|
||||||
[_fd]?: number;
|
|
||||||
[_path]: string;
|
|
||||||
[_readSize]: number;
|
|
||||||
[_reading]: boolean;
|
|
||||||
[_size]: number;
|
|
||||||
[_remain]: number;
|
|
||||||
[_autoClose]: boolean;
|
|
||||||
constructor(path: string, opt: ReadStreamOptions);
|
|
||||||
get fd(): number | undefined;
|
|
||||||
get path(): string;
|
|
||||||
write(): void;
|
|
||||||
end(): void;
|
|
||||||
[_open](): void;
|
|
||||||
[_onopen](er?: NodeJS.ErrnoException | null, fd?: number): void;
|
|
||||||
[_makeBuf](): Buffer;
|
|
||||||
[_read](): void;
|
|
||||||
[_onread](er?: NodeJS.ErrnoException | null, br?: number, buf?: Buffer): void;
|
|
||||||
[_close](): void;
|
|
||||||
[_onerror](er: NodeJS.ErrnoException): void;
|
|
||||||
[_handleChunk](br: number, buf: Buffer): boolean;
|
|
||||||
emit<Event extends keyof ReadStreamEvents>(ev: Event, ...args: ReadStreamEvents[Event]): boolean;
|
|
||||||
}
|
|
||||||
export declare class ReadStreamSync extends ReadStream {
|
|
||||||
[_open](): void;
|
|
||||||
[_read](): void;
|
|
||||||
[_close](): void;
|
|
||||||
}
|
|
||||||
export type WriteStreamOptions = {
|
|
||||||
fd?: number;
|
|
||||||
autoClose?: boolean;
|
|
||||||
mode?: number;
|
|
||||||
captureRejections?: boolean;
|
|
||||||
start?: number;
|
|
||||||
flags?: string;
|
|
||||||
};
|
|
||||||
export declare class WriteStream extends EE {
|
|
||||||
readable: false;
|
|
||||||
writable: boolean;
|
|
||||||
[_errored]: boolean;
|
|
||||||
[_writing]: boolean;
|
|
||||||
[_ended]: boolean;
|
|
||||||
[_queue]: Buffer[];
|
|
||||||
[_needDrain]: boolean;
|
|
||||||
[_path]: string;
|
|
||||||
[_mode]: number;
|
|
||||||
[_autoClose]: boolean;
|
|
||||||
[_fd]?: number;
|
|
||||||
[_defaultFlag]: boolean;
|
|
||||||
[_flags]: string;
|
|
||||||
[_finished]: boolean;
|
|
||||||
[_pos]?: number;
|
|
||||||
constructor(path: string, opt: WriteStreamOptions);
|
|
||||||
emit(ev: string, ...args: any[]): boolean;
|
|
||||||
get fd(): number | undefined;
|
|
||||||
get path(): string;
|
|
||||||
[_onerror](er: NodeJS.ErrnoException): void;
|
|
||||||
[_open](): void;
|
|
||||||
[_onopen](er?: null | NodeJS.ErrnoException, fd?: number): void;
|
|
||||||
end(buf: string, enc?: BufferEncoding): this;
|
|
||||||
end(buf?: Buffer, enc?: undefined): this;
|
|
||||||
write(buf: string, enc?: BufferEncoding): boolean;
|
|
||||||
write(buf: Buffer, enc?: undefined): boolean;
|
|
||||||
[_write](buf: Buffer): void;
|
|
||||||
[_onwrite](er?: null | NodeJS.ErrnoException, bw?: number): void;
|
|
||||||
[_flush](): void;
|
|
||||||
[_close](): void;
|
|
||||||
}
|
|
||||||
export declare class WriteStreamSync extends WriteStream {
|
|
||||||
[_open](): void;
|
|
||||||
[_close](): void;
|
|
||||||
[_write](buf: Buffer): void;
|
|
||||||
}
|
|
||||||
export {};
|
|
||||||
//# sourceMappingURL=index.d.ts.map
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,OAAO,EAAE,MAAM,QAAQ,CAAA;AAEvB,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAInC,QAAA,MAAM,UAAU,eAAuB,CAAA;AACvC,QAAA,MAAM,MAAM,eAAmB,CAAA;AAC/B,QAAA,MAAM,MAAM,eAAmB,CAAA;AAC/B,QAAA,MAAM,GAAG,eAAgB,CAAA;AACzB,QAAA,MAAM,SAAS,eAAsB,CAAA;AACrC,QAAA,MAAM,MAAM,eAAmB,CAAA;AAC/B,QAAA,MAAM,MAAM,eAAmB,CAAA;AAC/B,QAAA,MAAM,YAAY,eAAyB,CAAA;AAC3C,QAAA,MAAM,QAAQ,eAAqB,CAAA;AACnC,QAAA,MAAM,KAAK,eAAkB,CAAA;AAC7B,QAAA,MAAM,UAAU,eAAuB,CAAA;AACvC,QAAA,MAAM,QAAQ,eAAqB,CAAA;AACnC,QAAA,MAAM,OAAO,eAAoB,CAAA;AACjC,QAAA,MAAM,OAAO,eAAoB,CAAA;AACjC,QAAA,MAAM,QAAQ,eAAqB,CAAA;AACnC,QAAA,MAAM,KAAK,eAAkB,CAAA;AAC7B,QAAA,MAAM,KAAK,eAAkB,CAAA;AAC7B,QAAA,MAAM,IAAI,eAAiB,CAAA;AAC3B,QAAA,MAAM,MAAM,eAAmB,CAAA;AAC/B,QAAA,MAAM,KAAK,eAAkB,CAAA;AAC7B,QAAA,MAAM,SAAS,eAAsB,CAAA;AACrC,QAAA,MAAM,QAAQ,eAAqB,CAAA;AACnC,QAAA,MAAM,OAAO,eAAoB,CAAA;AACjC,QAAA,MAAM,KAAK,eAAkB,CAAA;AAC7B,QAAA,MAAM,MAAM,eAAmB,CAAA;AAC/B,QAAA,MAAM,QAAQ,eAAqB,CAAA;AACnC,QAAA,MAAM,YAAY,eAAyB,CAAA;AAC3C,QAAA,MAAM,QAAQ,eAAqB,CAAA;AAEnC,MAAM,MAAM,iBAAiB,GAC3B,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,GAAG;IAC1C,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,CAAA;AAEH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,GAAG;IACxE,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,CAAC,CAAA;CACnB,CAAA;AAED,qBAAa,UAAW,SAAQ,QAAQ,CACtC,QAAQ,CAAC,cAAc,EACvB,MAAM,EACN,gBAAgB,CACjB;IACC,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAS;IAC5B,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAChB,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IACpB,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAS;IAC5B,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAChB,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAClB,CAAC,UAAU,CAAC,EAAE,OAAO,CAAA;gBAET,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,iBAAiB;IA4BhD,IAAI,EAAE,uBAEL;IAED,IAAI,IAAI,WAEP;IAGD,KAAK;IAKL,GAAG;IAIH,CAAC,KAAK,CAAC;IAIP,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,cAAc,GAAG,IAAI,EAAE,EAAE,CAAC,EAAE,MAAM;IAUxD,CAAC,QAAQ,CAAC;IAIV,CAAC,KAAK,CAAC;IAeP,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,cAAc,GAAG,IAAI,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM;IAStE,CAAC,MAAM,CAAC;IAUR,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,cAAc;IAMpC,CAAC,YAAY,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM;IAiBtC,IAAI,CAAC,KAAK,SAAS,MAAM,gBAAgB,EACvC,EAAE,EAAE,KAAK,EACT,GAAG,IAAI,EAAE,gBAAgB,CAAC,KAAK,CAAC,GAC/B,OAAO;CAuBX;AAED,qBAAa,cAAe,SAAQ,UAAU;IAC5C,CAAC,KAAK,CAAC;IAYP,CAAC,KAAK,CAAC;IA2BP,CAAC,MAAM,CAAC;CAQT;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAED,qBAAa,WAAY,SAAQ,EAAE;IACjC,QAAQ,EAAE,KAAK,CAAQ;IACvB,QAAQ,EAAE,OAAO,CAAQ;IACzB,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAS;IAC5B,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAS;IAC5B,CAAC,MAAM,CAAC,EAAE,OAAO,CAAS;IAC1B,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAM;IACxB,CAAC,UAAU,CAAC,EAAE,OAAO,CAAS;IAC9B,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAChB,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAChB,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IACtB,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IACxB,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,SAAS,CAAC,EAAE,OAAO,CAAS;IAC7B,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAA;gBAEH,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,kBAAkB;IAoBjD,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE;IAU/B,IAAI,EAAE,uBAEL;IAED,IAAI,IAAI,WAEP;IAED,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,cAAc;IAMpC,CAAC,KAAK,CAAC;IAMP,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,MAAM;IAoBxD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,cAAc,GAAG,IAAI;IAC5C,GAAG,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,IAAI;IAoBxC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,cAAc,GAAG,OAAO;IACjD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,OAAO;IAsB5C,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,MAAM;IAWpB,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,MAAM;IAwBzD,CAAC,MAAM,CAAC;IAgBR,CAAC,MAAM,CAAC;CAST;AAED,qBAAa,eAAgB,SAAQ,WAAW;IAC9C,CAAC,KAAK,CAAC,IAAI,IAAI;IAsBf,CAAC,MAAM,CAAC;IASR,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,MAAM;CAmBrB"}
|
|
||||||
|
|
@ -1,430 +0,0 @@
|
||||||
"use strict";
|
|
||||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
||||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
||||||
};
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.WriteStreamSync = exports.WriteStream = exports.ReadStreamSync = exports.ReadStream = void 0;
|
|
||||||
const events_1 = __importDefault(require("events"));
|
|
||||||
const fs_1 = __importDefault(require("fs"));
|
|
||||||
const minipass_1 = require("minipass");
|
|
||||||
const writev = fs_1.default.writev;
|
|
||||||
const _autoClose = Symbol('_autoClose');
|
|
||||||
const _close = Symbol('_close');
|
|
||||||
const _ended = Symbol('_ended');
|
|
||||||
const _fd = Symbol('_fd');
|
|
||||||
const _finished = Symbol('_finished');
|
|
||||||
const _flags = Symbol('_flags');
|
|
||||||
const _flush = Symbol('_flush');
|
|
||||||
const _handleChunk = Symbol('_handleChunk');
|
|
||||||
const _makeBuf = Symbol('_makeBuf');
|
|
||||||
const _mode = Symbol('_mode');
|
|
||||||
const _needDrain = Symbol('_needDrain');
|
|
||||||
const _onerror = Symbol('_onerror');
|
|
||||||
const _onopen = Symbol('_onopen');
|
|
||||||
const _onread = Symbol('_onread');
|
|
||||||
const _onwrite = Symbol('_onwrite');
|
|
||||||
const _open = Symbol('_open');
|
|
||||||
const _path = Symbol('_path');
|
|
||||||
const _pos = Symbol('_pos');
|
|
||||||
const _queue = Symbol('_queue');
|
|
||||||
const _read = Symbol('_read');
|
|
||||||
const _readSize = Symbol('_readSize');
|
|
||||||
const _reading = Symbol('_reading');
|
|
||||||
const _remain = Symbol('_remain');
|
|
||||||
const _size = Symbol('_size');
|
|
||||||
const _write = Symbol('_write');
|
|
||||||
const _writing = Symbol('_writing');
|
|
||||||
const _defaultFlag = Symbol('_defaultFlag');
|
|
||||||
const _errored = Symbol('_errored');
|
|
||||||
class ReadStream extends minipass_1.Minipass {
|
|
||||||
[_errored] = false;
|
|
||||||
[_fd];
|
|
||||||
[_path];
|
|
||||||
[_readSize];
|
|
||||||
[_reading] = false;
|
|
||||||
[_size];
|
|
||||||
[_remain];
|
|
||||||
[_autoClose];
|
|
||||||
constructor(path, opt) {
|
|
||||||
opt = opt || {};
|
|
||||||
super(opt);
|
|
||||||
this.readable = true;
|
|
||||||
this.writable = false;
|
|
||||||
if (typeof path !== 'string') {
|
|
||||||
throw new TypeError('path must be a string');
|
|
||||||
}
|
|
||||||
this[_errored] = false;
|
|
||||||
this[_fd] = typeof opt.fd === 'number' ? opt.fd : undefined;
|
|
||||||
this[_path] = path;
|
|
||||||
this[_readSize] = opt.readSize || 16 * 1024 * 1024;
|
|
||||||
this[_reading] = false;
|
|
||||||
this[_size] = typeof opt.size === 'number' ? opt.size : Infinity;
|
|
||||||
this[_remain] = this[_size];
|
|
||||||
this[_autoClose] =
|
|
||||||
typeof opt.autoClose === 'boolean' ? opt.autoClose : true;
|
|
||||||
if (typeof this[_fd] === 'number') {
|
|
||||||
this[_read]();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this[_open]();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
get fd() {
|
|
||||||
return this[_fd];
|
|
||||||
}
|
|
||||||
get path() {
|
|
||||||
return this[_path];
|
|
||||||
}
|
|
||||||
//@ts-ignore
|
|
||||||
write() {
|
|
||||||
throw new TypeError('this is a readable stream');
|
|
||||||
}
|
|
||||||
//@ts-ignore
|
|
||||||
end() {
|
|
||||||
throw new TypeError('this is a readable stream');
|
|
||||||
}
|
|
||||||
[_open]() {
|
|
||||||
fs_1.default.open(this[_path], 'r', (er, fd) => this[_onopen](er, fd));
|
|
||||||
}
|
|
||||||
[_onopen](er, fd) {
|
|
||||||
if (er) {
|
|
||||||
this[_onerror](er);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this[_fd] = fd;
|
|
||||||
this.emit('open', fd);
|
|
||||||
this[_read]();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[_makeBuf]() {
|
|
||||||
return Buffer.allocUnsafe(Math.min(this[_readSize], this[_remain]));
|
|
||||||
}
|
|
||||||
[_read]() {
|
|
||||||
if (!this[_reading]) {
|
|
||||||
this[_reading] = true;
|
|
||||||
const buf = this[_makeBuf]();
|
|
||||||
/* c8 ignore start */
|
|
||||||
if (buf.length === 0) {
|
|
||||||
return process.nextTick(() => this[_onread](null, 0, buf));
|
|
||||||
}
|
|
||||||
/* c8 ignore stop */
|
|
||||||
fs_1.default.read(this[_fd], buf, 0, buf.length, null, (er, br, b) => this[_onread](er, br, b));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[_onread](er, br, buf) {
|
|
||||||
this[_reading] = false;
|
|
||||||
if (er) {
|
|
||||||
this[_onerror](er);
|
|
||||||
}
|
|
||||||
else if (this[_handleChunk](br, buf)) {
|
|
||||||
this[_read]();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[_close]() {
|
|
||||||
if (this[_autoClose] && typeof this[_fd] === 'number') {
|
|
||||||
const fd = this[_fd];
|
|
||||||
this[_fd] = undefined;
|
|
||||||
fs_1.default.close(fd, er => er ? this.emit('error', er) : this.emit('close'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[_onerror](er) {
|
|
||||||
this[_reading] = true;
|
|
||||||
this[_close]();
|
|
||||||
this.emit('error', er);
|
|
||||||
}
|
|
||||||
[_handleChunk](br, buf) {
|
|
||||||
let ret = false;
|
|
||||||
// no effect if infinite
|
|
||||||
this[_remain] -= br;
|
|
||||||
if (br > 0) {
|
|
||||||
ret = super.write(br < buf.length ? buf.subarray(0, br) : buf);
|
|
||||||
}
|
|
||||||
if (br === 0 || this[_remain] <= 0) {
|
|
||||||
ret = false;
|
|
||||||
this[_close]();
|
|
||||||
super.end();
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
emit(ev, ...args) {
|
|
||||||
switch (ev) {
|
|
||||||
case 'prefinish':
|
|
||||||
case 'finish':
|
|
||||||
return false;
|
|
||||||
case 'drain':
|
|
||||||
if (typeof this[_fd] === 'number') {
|
|
||||||
this[_read]();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
case 'error':
|
|
||||||
if (this[_errored]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this[_errored] = true;
|
|
||||||
return super.emit(ev, ...args);
|
|
||||||
default:
|
|
||||||
return super.emit(ev, ...args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.ReadStream = ReadStream;
|
|
||||||
class ReadStreamSync extends ReadStream {
|
|
||||||
[_open]() {
|
|
||||||
let threw = true;
|
|
||||||
try {
|
|
||||||
this[_onopen](null, fs_1.default.openSync(this[_path], 'r'));
|
|
||||||
threw = false;
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (threw) {
|
|
||||||
this[_close]();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[_read]() {
|
|
||||||
let threw = true;
|
|
||||||
try {
|
|
||||||
if (!this[_reading]) {
|
|
||||||
this[_reading] = true;
|
|
||||||
do {
|
|
||||||
const buf = this[_makeBuf]();
|
|
||||||
/* c8 ignore start */
|
|
||||||
const br = buf.length === 0
|
|
||||||
? 0
|
|
||||||
: fs_1.default.readSync(this[_fd], buf, 0, buf.length, null);
|
|
||||||
/* c8 ignore stop */
|
|
||||||
if (!this[_handleChunk](br, buf)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while (true);
|
|
||||||
this[_reading] = false;
|
|
||||||
}
|
|
||||||
threw = false;
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (threw) {
|
|
||||||
this[_close]();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[_close]() {
|
|
||||||
if (this[_autoClose] && typeof this[_fd] === 'number') {
|
|
||||||
const fd = this[_fd];
|
|
||||||
this[_fd] = undefined;
|
|
||||||
fs_1.default.closeSync(fd);
|
|
||||||
this.emit('close');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.ReadStreamSync = ReadStreamSync;
|
|
||||||
class WriteStream extends events_1.default {
|
|
||||||
readable = false;
|
|
||||||
writable = true;
|
|
||||||
[_errored] = false;
|
|
||||||
[_writing] = false;
|
|
||||||
[_ended] = false;
|
|
||||||
[_queue] = [];
|
|
||||||
[_needDrain] = false;
|
|
||||||
[_path];
|
|
||||||
[_mode];
|
|
||||||
[_autoClose];
|
|
||||||
[_fd];
|
|
||||||
[_defaultFlag];
|
|
||||||
[_flags];
|
|
||||||
[_finished] = false;
|
|
||||||
[_pos];
|
|
||||||
constructor(path, opt) {
|
|
||||||
opt = opt || {};
|
|
||||||
super(opt);
|
|
||||||
this[_path] = path;
|
|
||||||
this[_fd] = typeof opt.fd === 'number' ? opt.fd : undefined;
|
|
||||||
this[_mode] = opt.mode === undefined ? 0o666 : opt.mode;
|
|
||||||
this[_pos] = typeof opt.start === 'number' ? opt.start : undefined;
|
|
||||||
this[_autoClose] =
|
|
||||||
typeof opt.autoClose === 'boolean' ? opt.autoClose : true;
|
|
||||||
// truncating makes no sense when writing into the middle
|
|
||||||
const defaultFlag = this[_pos] !== undefined ? 'r+' : 'w';
|
|
||||||
this[_defaultFlag] = opt.flags === undefined;
|
|
||||||
this[_flags] = opt.flags === undefined ? defaultFlag : opt.flags;
|
|
||||||
if (this[_fd] === undefined) {
|
|
||||||
this[_open]();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
emit(ev, ...args) {
|
|
||||||
if (ev === 'error') {
|
|
||||||
if (this[_errored]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this[_errored] = true;
|
|
||||||
}
|
|
||||||
return super.emit(ev, ...args);
|
|
||||||
}
|
|
||||||
get fd() {
|
|
||||||
return this[_fd];
|
|
||||||
}
|
|
||||||
get path() {
|
|
||||||
return this[_path];
|
|
||||||
}
|
|
||||||
[_onerror](er) {
|
|
||||||
this[_close]();
|
|
||||||
this[_writing] = true;
|
|
||||||
this.emit('error', er);
|
|
||||||
}
|
|
||||||
[_open]() {
|
|
||||||
fs_1.default.open(this[_path], this[_flags], this[_mode], (er, fd) => this[_onopen](er, fd));
|
|
||||||
}
|
|
||||||
[_onopen](er, fd) {
|
|
||||||
if (this[_defaultFlag] &&
|
|
||||||
this[_flags] === 'r+' &&
|
|
||||||
er &&
|
|
||||||
er.code === 'ENOENT') {
|
|
||||||
this[_flags] = 'w';
|
|
||||||
this[_open]();
|
|
||||||
}
|
|
||||||
else if (er) {
|
|
||||||
this[_onerror](er);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this[_fd] = fd;
|
|
||||||
this.emit('open', fd);
|
|
||||||
if (!this[_writing]) {
|
|
||||||
this[_flush]();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end(buf, enc) {
|
|
||||||
if (buf) {
|
|
||||||
//@ts-ignore
|
|
||||||
this.write(buf, enc);
|
|
||||||
}
|
|
||||||
this[_ended] = true;
|
|
||||||
// synthetic after-write logic, where drain/finish live
|
|
||||||
if (!this[_writing] &&
|
|
||||||
!this[_queue].length &&
|
|
||||||
typeof this[_fd] === 'number') {
|
|
||||||
this[_onwrite](null, 0);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
write(buf, enc) {
|
|
||||||
if (typeof buf === 'string') {
|
|
||||||
buf = Buffer.from(buf, enc);
|
|
||||||
}
|
|
||||||
if (this[_ended]) {
|
|
||||||
this.emit('error', new Error('write() after end()'));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (this[_fd] === undefined || this[_writing] || this[_queue].length) {
|
|
||||||
this[_queue].push(buf);
|
|
||||||
this[_needDrain] = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this[_writing] = true;
|
|
||||||
this[_write](buf);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
[_write](buf) {
|
|
||||||
fs_1.default.write(this[_fd], buf, 0, buf.length, this[_pos], (er, bw) => this[_onwrite](er, bw));
|
|
||||||
}
|
|
||||||
[_onwrite](er, bw) {
|
|
||||||
if (er) {
|
|
||||||
this[_onerror](er);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (this[_pos] !== undefined && typeof bw === 'number') {
|
|
||||||
this[_pos] += bw;
|
|
||||||
}
|
|
||||||
if (this[_queue].length) {
|
|
||||||
this[_flush]();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this[_writing] = false;
|
|
||||||
if (this[_ended] && !this[_finished]) {
|
|
||||||
this[_finished] = true;
|
|
||||||
this[_close]();
|
|
||||||
this.emit('finish');
|
|
||||||
}
|
|
||||||
else if (this[_needDrain]) {
|
|
||||||
this[_needDrain] = false;
|
|
||||||
this.emit('drain');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[_flush]() {
|
|
||||||
if (this[_queue].length === 0) {
|
|
||||||
if (this[_ended]) {
|
|
||||||
this[_onwrite](null, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (this[_queue].length === 1) {
|
|
||||||
this[_write](this[_queue].pop());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const iovec = this[_queue];
|
|
||||||
this[_queue] = [];
|
|
||||||
writev(this[_fd], iovec, this[_pos], (er, bw) => this[_onwrite](er, bw));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[_close]() {
|
|
||||||
if (this[_autoClose] && typeof this[_fd] === 'number') {
|
|
||||||
const fd = this[_fd];
|
|
||||||
this[_fd] = undefined;
|
|
||||||
fs_1.default.close(fd, er => er ? this.emit('error', er) : this.emit('close'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.WriteStream = WriteStream;
|
|
||||||
class WriteStreamSync extends WriteStream {
|
|
||||||
[_open]() {
|
|
||||||
let fd;
|
|
||||||
// only wrap in a try{} block if we know we'll retry, to avoid
|
|
||||||
// the rethrow obscuring the error's source frame in most cases.
|
|
||||||
if (this[_defaultFlag] && this[_flags] === 'r+') {
|
|
||||||
try {
|
|
||||||
fd = fs_1.default.openSync(this[_path], this[_flags], this[_mode]);
|
|
||||||
}
|
|
||||||
catch (er) {
|
|
||||||
if (er?.code === 'ENOENT') {
|
|
||||||
this[_flags] = 'w';
|
|
||||||
return this[_open]();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw er;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
fd = fs_1.default.openSync(this[_path], this[_flags], this[_mode]);
|
|
||||||
}
|
|
||||||
this[_onopen](null, fd);
|
|
||||||
}
|
|
||||||
[_close]() {
|
|
||||||
if (this[_autoClose] && typeof this[_fd] === 'number') {
|
|
||||||
const fd = this[_fd];
|
|
||||||
this[_fd] = undefined;
|
|
||||||
fs_1.default.closeSync(fd);
|
|
||||||
this.emit('close');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[_write](buf) {
|
|
||||||
// throw the original, but try to close if it fails
|
|
||||||
let threw = true;
|
|
||||||
try {
|
|
||||||
this[_onwrite](null, fs_1.default.writeSync(this[_fd], buf, 0, buf.length, this[_pos]));
|
|
||||||
threw = false;
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (threw) {
|
|
||||||
try {
|
|
||||||
this[_close]();
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
// ok error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.WriteStreamSync = WriteStreamSync;
|
|
||||||
//# sourceMappingURL=index.js.map
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"type": "commonjs"
|
|
||||||
}
|
|
||||||
|
|
@ -1,118 +0,0 @@
|
||||||
/// <reference types="node" resolution-mode="require"/>
|
|
||||||
/// <reference types="node" resolution-mode="require"/>
|
|
||||||
/// <reference types="node" resolution-mode="require"/>
|
|
||||||
import EE from 'events';
|
|
||||||
import { Minipass } from 'minipass';
|
|
||||||
declare const _autoClose: unique symbol;
|
|
||||||
declare const _close: unique symbol;
|
|
||||||
declare const _ended: unique symbol;
|
|
||||||
declare const _fd: unique symbol;
|
|
||||||
declare const _finished: unique symbol;
|
|
||||||
declare const _flags: unique symbol;
|
|
||||||
declare const _flush: unique symbol;
|
|
||||||
declare const _handleChunk: unique symbol;
|
|
||||||
declare const _makeBuf: unique symbol;
|
|
||||||
declare const _mode: unique symbol;
|
|
||||||
declare const _needDrain: unique symbol;
|
|
||||||
declare const _onerror: unique symbol;
|
|
||||||
declare const _onopen: unique symbol;
|
|
||||||
declare const _onread: unique symbol;
|
|
||||||
declare const _onwrite: unique symbol;
|
|
||||||
declare const _open: unique symbol;
|
|
||||||
declare const _path: unique symbol;
|
|
||||||
declare const _pos: unique symbol;
|
|
||||||
declare const _queue: unique symbol;
|
|
||||||
declare const _read: unique symbol;
|
|
||||||
declare const _readSize: unique symbol;
|
|
||||||
declare const _reading: unique symbol;
|
|
||||||
declare const _remain: unique symbol;
|
|
||||||
declare const _size: unique symbol;
|
|
||||||
declare const _write: unique symbol;
|
|
||||||
declare const _writing: unique symbol;
|
|
||||||
declare const _defaultFlag: unique symbol;
|
|
||||||
declare const _errored: unique symbol;
|
|
||||||
export type ReadStreamOptions = Minipass.Options<Minipass.ContiguousData> & {
|
|
||||||
fd?: number;
|
|
||||||
readSize?: number;
|
|
||||||
size?: number;
|
|
||||||
autoClose?: boolean;
|
|
||||||
};
|
|
||||||
export type ReadStreamEvents = Minipass.Events<Minipass.ContiguousData> & {
|
|
||||||
open: [fd: number];
|
|
||||||
};
|
|
||||||
export declare class ReadStream extends Minipass<Minipass.ContiguousData, Buffer, ReadStreamEvents> {
|
|
||||||
[_errored]: boolean;
|
|
||||||
[_fd]?: number;
|
|
||||||
[_path]: string;
|
|
||||||
[_readSize]: number;
|
|
||||||
[_reading]: boolean;
|
|
||||||
[_size]: number;
|
|
||||||
[_remain]: number;
|
|
||||||
[_autoClose]: boolean;
|
|
||||||
constructor(path: string, opt: ReadStreamOptions);
|
|
||||||
get fd(): number | undefined;
|
|
||||||
get path(): string;
|
|
||||||
write(): void;
|
|
||||||
end(): void;
|
|
||||||
[_open](): void;
|
|
||||||
[_onopen](er?: NodeJS.ErrnoException | null, fd?: number): void;
|
|
||||||
[_makeBuf](): Buffer;
|
|
||||||
[_read](): void;
|
|
||||||
[_onread](er?: NodeJS.ErrnoException | null, br?: number, buf?: Buffer): void;
|
|
||||||
[_close](): void;
|
|
||||||
[_onerror](er: NodeJS.ErrnoException): void;
|
|
||||||
[_handleChunk](br: number, buf: Buffer): boolean;
|
|
||||||
emit<Event extends keyof ReadStreamEvents>(ev: Event, ...args: ReadStreamEvents[Event]): boolean;
|
|
||||||
}
|
|
||||||
export declare class ReadStreamSync extends ReadStream {
|
|
||||||
[_open](): void;
|
|
||||||
[_read](): void;
|
|
||||||
[_close](): void;
|
|
||||||
}
|
|
||||||
export type WriteStreamOptions = {
|
|
||||||
fd?: number;
|
|
||||||
autoClose?: boolean;
|
|
||||||
mode?: number;
|
|
||||||
captureRejections?: boolean;
|
|
||||||
start?: number;
|
|
||||||
flags?: string;
|
|
||||||
};
|
|
||||||
export declare class WriteStream extends EE {
|
|
||||||
readable: false;
|
|
||||||
writable: boolean;
|
|
||||||
[_errored]: boolean;
|
|
||||||
[_writing]: boolean;
|
|
||||||
[_ended]: boolean;
|
|
||||||
[_queue]: Buffer[];
|
|
||||||
[_needDrain]: boolean;
|
|
||||||
[_path]: string;
|
|
||||||
[_mode]: number;
|
|
||||||
[_autoClose]: boolean;
|
|
||||||
[_fd]?: number;
|
|
||||||
[_defaultFlag]: boolean;
|
|
||||||
[_flags]: string;
|
|
||||||
[_finished]: boolean;
|
|
||||||
[_pos]?: number;
|
|
||||||
constructor(path: string, opt: WriteStreamOptions);
|
|
||||||
emit(ev: string, ...args: any[]): boolean;
|
|
||||||
get fd(): number | undefined;
|
|
||||||
get path(): string;
|
|
||||||
[_onerror](er: NodeJS.ErrnoException): void;
|
|
||||||
[_open](): void;
|
|
||||||
[_onopen](er?: null | NodeJS.ErrnoException, fd?: number): void;
|
|
||||||
end(buf: string, enc?: BufferEncoding): this;
|
|
||||||
end(buf?: Buffer, enc?: undefined): this;
|
|
||||||
write(buf: string, enc?: BufferEncoding): boolean;
|
|
||||||
write(buf: Buffer, enc?: undefined): boolean;
|
|
||||||
[_write](buf: Buffer): void;
|
|
||||||
[_onwrite](er?: null | NodeJS.ErrnoException, bw?: number): void;
|
|
||||||
[_flush](): void;
|
|
||||||
[_close](): void;
|
|
||||||
}
|
|
||||||
export declare class WriteStreamSync extends WriteStream {
|
|
||||||
[_open](): void;
|
|
||||||
[_close](): void;
|
|
||||||
[_write](buf: Buffer): void;
|
|
||||||
}
|
|
||||||
export {};
|
|
||||||
//# sourceMappingURL=index.d.ts.map
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,OAAO,EAAE,MAAM,QAAQ,CAAA;AAEvB,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAInC,QAAA,MAAM,UAAU,eAAuB,CAAA;AACvC,QAAA,MAAM,MAAM,eAAmB,CAAA;AAC/B,QAAA,MAAM,MAAM,eAAmB,CAAA;AAC/B,QAAA,MAAM,GAAG,eAAgB,CAAA;AACzB,QAAA,MAAM,SAAS,eAAsB,CAAA;AACrC,QAAA,MAAM,MAAM,eAAmB,CAAA;AAC/B,QAAA,MAAM,MAAM,eAAmB,CAAA;AAC/B,QAAA,MAAM,YAAY,eAAyB,CAAA;AAC3C,QAAA,MAAM,QAAQ,eAAqB,CAAA;AACnC,QAAA,MAAM,KAAK,eAAkB,CAAA;AAC7B,QAAA,MAAM,UAAU,eAAuB,CAAA;AACvC,QAAA,MAAM,QAAQ,eAAqB,CAAA;AACnC,QAAA,MAAM,OAAO,eAAoB,CAAA;AACjC,QAAA,MAAM,OAAO,eAAoB,CAAA;AACjC,QAAA,MAAM,QAAQ,eAAqB,CAAA;AACnC,QAAA,MAAM,KAAK,eAAkB,CAAA;AAC7B,QAAA,MAAM,KAAK,eAAkB,CAAA;AAC7B,QAAA,MAAM,IAAI,eAAiB,CAAA;AAC3B,QAAA,MAAM,MAAM,eAAmB,CAAA;AAC/B,QAAA,MAAM,KAAK,eAAkB,CAAA;AAC7B,QAAA,MAAM,SAAS,eAAsB,CAAA;AACrC,QAAA,MAAM,QAAQ,eAAqB,CAAA;AACnC,QAAA,MAAM,OAAO,eAAoB,CAAA;AACjC,QAAA,MAAM,KAAK,eAAkB,CAAA;AAC7B,QAAA,MAAM,MAAM,eAAmB,CAAA;AAC/B,QAAA,MAAM,QAAQ,eAAqB,CAAA;AACnC,QAAA,MAAM,YAAY,eAAyB,CAAA;AAC3C,QAAA,MAAM,QAAQ,eAAqB,CAAA;AAEnC,MAAM,MAAM,iBAAiB,GAC3B,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,GAAG;IAC1C,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,CAAA;AAEH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAC,GAAG;IACxE,IAAI,EAAE,CAAC,EAAE,EAAE,MAAM,CAAC,CAAA;CACnB,CAAA;AAED,qBAAa,UAAW,SAAQ,QAAQ,CACtC,QAAQ,CAAC,cAAc,EACvB,MAAM,EACN,gBAAgB,CACjB;IACC,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAS;IAC5B,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAChB,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IACpB,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAS;IAC5B,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAChB,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAClB,CAAC,UAAU,CAAC,EAAE,OAAO,CAAA;gBAET,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,iBAAiB;IA4BhD,IAAI,EAAE,uBAEL;IAED,IAAI,IAAI,WAEP;IAGD,KAAK;IAKL,GAAG;IAIH,CAAC,KAAK,CAAC;IAIP,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,cAAc,GAAG,IAAI,EAAE,EAAE,CAAC,EAAE,MAAM;IAUxD,CAAC,QAAQ,CAAC;IAIV,CAAC,KAAK,CAAC;IAeP,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,cAAc,GAAG,IAAI,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM;IAStE,CAAC,MAAM,CAAC;IAUR,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,cAAc;IAMpC,CAAC,YAAY,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM;IAiBtC,IAAI,CAAC,KAAK,SAAS,MAAM,gBAAgB,EACvC,EAAE,EAAE,KAAK,EACT,GAAG,IAAI,EAAE,gBAAgB,CAAC,KAAK,CAAC,GAC/B,OAAO;CAuBX;AAED,qBAAa,cAAe,SAAQ,UAAU;IAC5C,CAAC,KAAK,CAAC;IAYP,CAAC,KAAK,CAAC;IA2BP,CAAC,MAAM,CAAC;CAQT;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAED,qBAAa,WAAY,SAAQ,EAAE;IACjC,QAAQ,EAAE,KAAK,CAAQ;IACvB,QAAQ,EAAE,OAAO,CAAQ;IACzB,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAS;IAC5B,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAS;IAC5B,CAAC,MAAM,CAAC,EAAE,OAAO,CAAS;IAC1B,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAM;IACxB,CAAC,UAAU,CAAC,EAAE,OAAO,CAAS;IAC9B,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAChB,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAChB,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IACtB,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC;IACf,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IACxB,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,SAAS,CAAC,EAAE,OAAO,CAAS;IAC7B,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAA;gBAEH,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,kBAAkB;IAoBjD,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE;IAU/B,IAAI,EAAE,uBAEL;IAED,IAAI,IAAI,WAEP;IAED,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,cAAc;IAMpC,CAAC,KAAK,CAAC;IAMP,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,MAAM;IAoBxD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,cAAc,GAAG,IAAI;IAC5C,GAAG,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,IAAI;IAoBxC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,cAAc,GAAG,OAAO;IACjD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,OAAO;IAsB5C,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,MAAM;IAWpB,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC,cAAc,EAAE,EAAE,CAAC,EAAE,MAAM;IAwBzD,CAAC,MAAM,CAAC;IAgBR,CAAC,MAAM,CAAC;CAST;AAED,qBAAa,eAAgB,SAAQ,WAAW;IAC9C,CAAC,KAAK,CAAC,IAAI,IAAI;IAsBf,CAAC,MAAM,CAAC;IASR,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,MAAM;CAmBrB"}
|
|
||||||
|
|
@ -1,420 +0,0 @@
|
||||||
import EE from 'events';
|
|
||||||
import fs from 'fs';
|
|
||||||
import { Minipass } from 'minipass';
|
|
||||||
const writev = fs.writev;
|
|
||||||
const _autoClose = Symbol('_autoClose');
|
|
||||||
const _close = Symbol('_close');
|
|
||||||
const _ended = Symbol('_ended');
|
|
||||||
const _fd = Symbol('_fd');
|
|
||||||
const _finished = Symbol('_finished');
|
|
||||||
const _flags = Symbol('_flags');
|
|
||||||
const _flush = Symbol('_flush');
|
|
||||||
const _handleChunk = Symbol('_handleChunk');
|
|
||||||
const _makeBuf = Symbol('_makeBuf');
|
|
||||||
const _mode = Symbol('_mode');
|
|
||||||
const _needDrain = Symbol('_needDrain');
|
|
||||||
const _onerror = Symbol('_onerror');
|
|
||||||
const _onopen = Symbol('_onopen');
|
|
||||||
const _onread = Symbol('_onread');
|
|
||||||
const _onwrite = Symbol('_onwrite');
|
|
||||||
const _open = Symbol('_open');
|
|
||||||
const _path = Symbol('_path');
|
|
||||||
const _pos = Symbol('_pos');
|
|
||||||
const _queue = Symbol('_queue');
|
|
||||||
const _read = Symbol('_read');
|
|
||||||
const _readSize = Symbol('_readSize');
|
|
||||||
const _reading = Symbol('_reading');
|
|
||||||
const _remain = Symbol('_remain');
|
|
||||||
const _size = Symbol('_size');
|
|
||||||
const _write = Symbol('_write');
|
|
||||||
const _writing = Symbol('_writing');
|
|
||||||
const _defaultFlag = Symbol('_defaultFlag');
|
|
||||||
const _errored = Symbol('_errored');
|
|
||||||
export class ReadStream extends Minipass {
|
|
||||||
[_errored] = false;
|
|
||||||
[_fd];
|
|
||||||
[_path];
|
|
||||||
[_readSize];
|
|
||||||
[_reading] = false;
|
|
||||||
[_size];
|
|
||||||
[_remain];
|
|
||||||
[_autoClose];
|
|
||||||
constructor(path, opt) {
|
|
||||||
opt = opt || {};
|
|
||||||
super(opt);
|
|
||||||
this.readable = true;
|
|
||||||
this.writable = false;
|
|
||||||
if (typeof path !== 'string') {
|
|
||||||
throw new TypeError('path must be a string');
|
|
||||||
}
|
|
||||||
this[_errored] = false;
|
|
||||||
this[_fd] = typeof opt.fd === 'number' ? opt.fd : undefined;
|
|
||||||
this[_path] = path;
|
|
||||||
this[_readSize] = opt.readSize || 16 * 1024 * 1024;
|
|
||||||
this[_reading] = false;
|
|
||||||
this[_size] = typeof opt.size === 'number' ? opt.size : Infinity;
|
|
||||||
this[_remain] = this[_size];
|
|
||||||
this[_autoClose] =
|
|
||||||
typeof opt.autoClose === 'boolean' ? opt.autoClose : true;
|
|
||||||
if (typeof this[_fd] === 'number') {
|
|
||||||
this[_read]();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this[_open]();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
get fd() {
|
|
||||||
return this[_fd];
|
|
||||||
}
|
|
||||||
get path() {
|
|
||||||
return this[_path];
|
|
||||||
}
|
|
||||||
//@ts-ignore
|
|
||||||
write() {
|
|
||||||
throw new TypeError('this is a readable stream');
|
|
||||||
}
|
|
||||||
//@ts-ignore
|
|
||||||
end() {
|
|
||||||
throw new TypeError('this is a readable stream');
|
|
||||||
}
|
|
||||||
[_open]() {
|
|
||||||
fs.open(this[_path], 'r', (er, fd) => this[_onopen](er, fd));
|
|
||||||
}
|
|
||||||
[_onopen](er, fd) {
|
|
||||||
if (er) {
|
|
||||||
this[_onerror](er);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this[_fd] = fd;
|
|
||||||
this.emit('open', fd);
|
|
||||||
this[_read]();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[_makeBuf]() {
|
|
||||||
return Buffer.allocUnsafe(Math.min(this[_readSize], this[_remain]));
|
|
||||||
}
|
|
||||||
[_read]() {
|
|
||||||
if (!this[_reading]) {
|
|
||||||
this[_reading] = true;
|
|
||||||
const buf = this[_makeBuf]();
|
|
||||||
/* c8 ignore start */
|
|
||||||
if (buf.length === 0) {
|
|
||||||
return process.nextTick(() => this[_onread](null, 0, buf));
|
|
||||||
}
|
|
||||||
/* c8 ignore stop */
|
|
||||||
fs.read(this[_fd], buf, 0, buf.length, null, (er, br, b) => this[_onread](er, br, b));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[_onread](er, br, buf) {
|
|
||||||
this[_reading] = false;
|
|
||||||
if (er) {
|
|
||||||
this[_onerror](er);
|
|
||||||
}
|
|
||||||
else if (this[_handleChunk](br, buf)) {
|
|
||||||
this[_read]();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[_close]() {
|
|
||||||
if (this[_autoClose] && typeof this[_fd] === 'number') {
|
|
||||||
const fd = this[_fd];
|
|
||||||
this[_fd] = undefined;
|
|
||||||
fs.close(fd, er => er ? this.emit('error', er) : this.emit('close'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[_onerror](er) {
|
|
||||||
this[_reading] = true;
|
|
||||||
this[_close]();
|
|
||||||
this.emit('error', er);
|
|
||||||
}
|
|
||||||
[_handleChunk](br, buf) {
|
|
||||||
let ret = false;
|
|
||||||
// no effect if infinite
|
|
||||||
this[_remain] -= br;
|
|
||||||
if (br > 0) {
|
|
||||||
ret = super.write(br < buf.length ? buf.subarray(0, br) : buf);
|
|
||||||
}
|
|
||||||
if (br === 0 || this[_remain] <= 0) {
|
|
||||||
ret = false;
|
|
||||||
this[_close]();
|
|
||||||
super.end();
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
emit(ev, ...args) {
|
|
||||||
switch (ev) {
|
|
||||||
case 'prefinish':
|
|
||||||
case 'finish':
|
|
||||||
return false;
|
|
||||||
case 'drain':
|
|
||||||
if (typeof this[_fd] === 'number') {
|
|
||||||
this[_read]();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
case 'error':
|
|
||||||
if (this[_errored]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this[_errored] = true;
|
|
||||||
return super.emit(ev, ...args);
|
|
||||||
default:
|
|
||||||
return super.emit(ev, ...args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export class ReadStreamSync extends ReadStream {
|
|
||||||
[_open]() {
|
|
||||||
let threw = true;
|
|
||||||
try {
|
|
||||||
this[_onopen](null, fs.openSync(this[_path], 'r'));
|
|
||||||
threw = false;
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (threw) {
|
|
||||||
this[_close]();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[_read]() {
|
|
||||||
let threw = true;
|
|
||||||
try {
|
|
||||||
if (!this[_reading]) {
|
|
||||||
this[_reading] = true;
|
|
||||||
do {
|
|
||||||
const buf = this[_makeBuf]();
|
|
||||||
/* c8 ignore start */
|
|
||||||
const br = buf.length === 0
|
|
||||||
? 0
|
|
||||||
: fs.readSync(this[_fd], buf, 0, buf.length, null);
|
|
||||||
/* c8 ignore stop */
|
|
||||||
if (!this[_handleChunk](br, buf)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while (true);
|
|
||||||
this[_reading] = false;
|
|
||||||
}
|
|
||||||
threw = false;
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (threw) {
|
|
||||||
this[_close]();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[_close]() {
|
|
||||||
if (this[_autoClose] && typeof this[_fd] === 'number') {
|
|
||||||
const fd = this[_fd];
|
|
||||||
this[_fd] = undefined;
|
|
||||||
fs.closeSync(fd);
|
|
||||||
this.emit('close');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export class WriteStream extends EE {
|
|
||||||
readable = false;
|
|
||||||
writable = true;
|
|
||||||
[_errored] = false;
|
|
||||||
[_writing] = false;
|
|
||||||
[_ended] = false;
|
|
||||||
[_queue] = [];
|
|
||||||
[_needDrain] = false;
|
|
||||||
[_path];
|
|
||||||
[_mode];
|
|
||||||
[_autoClose];
|
|
||||||
[_fd];
|
|
||||||
[_defaultFlag];
|
|
||||||
[_flags];
|
|
||||||
[_finished] = false;
|
|
||||||
[_pos];
|
|
||||||
constructor(path, opt) {
|
|
||||||
opt = opt || {};
|
|
||||||
super(opt);
|
|
||||||
this[_path] = path;
|
|
||||||
this[_fd] = typeof opt.fd === 'number' ? opt.fd : undefined;
|
|
||||||
this[_mode] = opt.mode === undefined ? 0o666 : opt.mode;
|
|
||||||
this[_pos] = typeof opt.start === 'number' ? opt.start : undefined;
|
|
||||||
this[_autoClose] =
|
|
||||||
typeof opt.autoClose === 'boolean' ? opt.autoClose : true;
|
|
||||||
// truncating makes no sense when writing into the middle
|
|
||||||
const defaultFlag = this[_pos] !== undefined ? 'r+' : 'w';
|
|
||||||
this[_defaultFlag] = opt.flags === undefined;
|
|
||||||
this[_flags] = opt.flags === undefined ? defaultFlag : opt.flags;
|
|
||||||
if (this[_fd] === undefined) {
|
|
||||||
this[_open]();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
emit(ev, ...args) {
|
|
||||||
if (ev === 'error') {
|
|
||||||
if (this[_errored]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this[_errored] = true;
|
|
||||||
}
|
|
||||||
return super.emit(ev, ...args);
|
|
||||||
}
|
|
||||||
get fd() {
|
|
||||||
return this[_fd];
|
|
||||||
}
|
|
||||||
get path() {
|
|
||||||
return this[_path];
|
|
||||||
}
|
|
||||||
[_onerror](er) {
|
|
||||||
this[_close]();
|
|
||||||
this[_writing] = true;
|
|
||||||
this.emit('error', er);
|
|
||||||
}
|
|
||||||
[_open]() {
|
|
||||||
fs.open(this[_path], this[_flags], this[_mode], (er, fd) => this[_onopen](er, fd));
|
|
||||||
}
|
|
||||||
[_onopen](er, fd) {
|
|
||||||
if (this[_defaultFlag] &&
|
|
||||||
this[_flags] === 'r+' &&
|
|
||||||
er &&
|
|
||||||
er.code === 'ENOENT') {
|
|
||||||
this[_flags] = 'w';
|
|
||||||
this[_open]();
|
|
||||||
}
|
|
||||||
else if (er) {
|
|
||||||
this[_onerror](er);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this[_fd] = fd;
|
|
||||||
this.emit('open', fd);
|
|
||||||
if (!this[_writing]) {
|
|
||||||
this[_flush]();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end(buf, enc) {
|
|
||||||
if (buf) {
|
|
||||||
//@ts-ignore
|
|
||||||
this.write(buf, enc);
|
|
||||||
}
|
|
||||||
this[_ended] = true;
|
|
||||||
// synthetic after-write logic, where drain/finish live
|
|
||||||
if (!this[_writing] &&
|
|
||||||
!this[_queue].length &&
|
|
||||||
typeof this[_fd] === 'number') {
|
|
||||||
this[_onwrite](null, 0);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
write(buf, enc) {
|
|
||||||
if (typeof buf === 'string') {
|
|
||||||
buf = Buffer.from(buf, enc);
|
|
||||||
}
|
|
||||||
if (this[_ended]) {
|
|
||||||
this.emit('error', new Error('write() after end()'));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (this[_fd] === undefined || this[_writing] || this[_queue].length) {
|
|
||||||
this[_queue].push(buf);
|
|
||||||
this[_needDrain] = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this[_writing] = true;
|
|
||||||
this[_write](buf);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
[_write](buf) {
|
|
||||||
fs.write(this[_fd], buf, 0, buf.length, this[_pos], (er, bw) => this[_onwrite](er, bw));
|
|
||||||
}
|
|
||||||
[_onwrite](er, bw) {
|
|
||||||
if (er) {
|
|
||||||
this[_onerror](er);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (this[_pos] !== undefined && typeof bw === 'number') {
|
|
||||||
this[_pos] += bw;
|
|
||||||
}
|
|
||||||
if (this[_queue].length) {
|
|
||||||
this[_flush]();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this[_writing] = false;
|
|
||||||
if (this[_ended] && !this[_finished]) {
|
|
||||||
this[_finished] = true;
|
|
||||||
this[_close]();
|
|
||||||
this.emit('finish');
|
|
||||||
}
|
|
||||||
else if (this[_needDrain]) {
|
|
||||||
this[_needDrain] = false;
|
|
||||||
this.emit('drain');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[_flush]() {
|
|
||||||
if (this[_queue].length === 0) {
|
|
||||||
if (this[_ended]) {
|
|
||||||
this[_onwrite](null, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (this[_queue].length === 1) {
|
|
||||||
this[_write](this[_queue].pop());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const iovec = this[_queue];
|
|
||||||
this[_queue] = [];
|
|
||||||
writev(this[_fd], iovec, this[_pos], (er, bw) => this[_onwrite](er, bw));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[_close]() {
|
|
||||||
if (this[_autoClose] && typeof this[_fd] === 'number') {
|
|
||||||
const fd = this[_fd];
|
|
||||||
this[_fd] = undefined;
|
|
||||||
fs.close(fd, er => er ? this.emit('error', er) : this.emit('close'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export class WriteStreamSync extends WriteStream {
|
|
||||||
[_open]() {
|
|
||||||
let fd;
|
|
||||||
// only wrap in a try{} block if we know we'll retry, to avoid
|
|
||||||
// the rethrow obscuring the error's source frame in most cases.
|
|
||||||
if (this[_defaultFlag] && this[_flags] === 'r+') {
|
|
||||||
try {
|
|
||||||
fd = fs.openSync(this[_path], this[_flags], this[_mode]);
|
|
||||||
}
|
|
||||||
catch (er) {
|
|
||||||
if (er?.code === 'ENOENT') {
|
|
||||||
this[_flags] = 'w';
|
|
||||||
return this[_open]();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw er;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
fd = fs.openSync(this[_path], this[_flags], this[_mode]);
|
|
||||||
}
|
|
||||||
this[_onopen](null, fd);
|
|
||||||
}
|
|
||||||
[_close]() {
|
|
||||||
if (this[_autoClose] && typeof this[_fd] === 'number') {
|
|
||||||
const fd = this[_fd];
|
|
||||||
this[_fd] = undefined;
|
|
||||||
fs.closeSync(fd);
|
|
||||||
this.emit('close');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[_write](buf) {
|
|
||||||
// throw the original, but try to close if it fails
|
|
||||||
let threw = true;
|
|
||||||
try {
|
|
||||||
this[_onwrite](null, fs.writeSync(this[_fd], buf, 0, buf.length, this[_pos]));
|
|
||||||
threw = false;
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (threw) {
|
|
||||||
try {
|
|
||||||
this[_close]();
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
// ok error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//# sourceMappingURL=index.js.map
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"type": "module"
|
|
||||||
}
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@isaacs/fs-minipass",
|
|
||||||
"version": "4.0.1",
|
|
||||||
"main": "./dist/commonjs/index.js",
|
|
||||||
"scripts": {
|
|
||||||
"prepare": "tshy",
|
|
||||||
"pretest": "npm run prepare",
|
|
||||||
"test": "tap",
|
|
||||||
"preversion": "npm test",
|
|
||||||
"postversion": "npm publish",
|
|
||||||
"prepublishOnly": "git push origin --follow-tags",
|
|
||||||
"format": "prettier --write . --loglevel warn",
|
|
||||||
"typedoc": "typedoc --tsconfig .tshy/esm.json ./src/*.ts"
|
|
||||||
},
|
|
||||||
"keywords": [],
|
|
||||||
"author": "Isaac Z. Schlueter",
|
|
||||||
"license": "ISC",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/npm/fs-minipass.git"
|
|
||||||
},
|
|
||||||
"description": "fs read and write streams based on minipass",
|
|
||||||
"dependencies": {
|
|
||||||
"minipass": "^7.0.4"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/node": "^20.11.30",
|
|
||||||
"mutate-fs": "^2.1.1",
|
|
||||||
"prettier": "^3.2.5",
|
|
||||||
"tap": "^18.7.1",
|
|
||||||
"tshy": "^1.12.0",
|
|
||||||
"typedoc": "^0.25.12"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"dist"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18.0.0"
|
|
||||||
},
|
|
||||||
"tshy": {
|
|
||||||
"exports": {
|
|
||||||
"./package.json": "./package.json",
|
|
||||||
".": "./src/index.ts"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"exports": {
|
|
||||||
"./package.json": "./package.json",
|
|
||||||
".": {
|
|
||||||
"import": {
|
|
||||||
"types": "./dist/esm/index.d.ts",
|
|
||||||
"default": "./dist/esm/index.js"
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"types": "./dist/commonjs/index.d.ts",
|
|
||||||
"default": "./dist/commonjs/index.js"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"types": "./dist/commonjs/index.d.ts",
|
|
||||||
"type": "module",
|
|
||||||
"prettier": {
|
|
||||||
"semi": false,
|
|
||||||
"printWidth": 75,
|
|
||||||
"tabWidth": 2,
|
|
||||||
"useTabs": false,
|
|
||||||
"singleQuote": true,
|
|
||||||
"jsxSingleQuote": false,
|
|
||||||
"bracketSameLine": true,
|
|
||||||
"arrowParens": "avoid",
|
|
||||||
"endOfLine": "lf"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -224,4 +224,4 @@ Fastest is gen-mapping: decoded output
|
||||||
```
|
```
|
||||||
|
|
||||||
[source-map]: https://www.npmjs.com/package/source-map
|
[source-map]: https://www.npmjs.com/package/source-map
|
||||||
[trace-mapping]: https://github.com/jridgewell/trace-mapping
|
[trace-mapping]: https://github.com/jridgewell/sourcemaps/tree/main/packages/trace-mapping
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,19 @@
|
||||||
(function (global, factory, m) {
|
(function (global, factory) {
|
||||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(module, require('@jridgewell/sourcemap-codec'), require('@jridgewell/trace-mapping')) :
|
if (typeof exports === 'object' && typeof module !== 'undefined') {
|
||||||
typeof define === 'function' && define.amd ? define(['module', '@jridgewell/sourcemap-codec', '@jridgewell/trace-mapping'], factory) :
|
factory(module, require('@jridgewell/sourcemap-codec'), require('@jridgewell/trace-mapping'));
|
||||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(m = { exports: {} }, global.sourcemapCodec, global.traceMapping), global.genMapping = 'default' in m.exports ? m.exports.default : m.exports);
|
module.exports = def(module);
|
||||||
|
} else if (typeof define === 'function' && define.amd) {
|
||||||
|
define(['module', '@jridgewell/sourcemap-codec', '@jridgewell/trace-mapping'], function(mod) {
|
||||||
|
factory.apply(this, arguments);
|
||||||
|
mod.exports = def(mod);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const mod = { exports: {} };
|
||||||
|
factory(mod, global.sourcemapCodec, global.traceMapping);
|
||||||
|
global = typeof globalThis !== 'undefined' ? globalThis : global || self;
|
||||||
|
global.genMapping = def(mod);
|
||||||
|
}
|
||||||
|
function def(m) { return 'default' in m.exports ? m.exports.default : m.exports; }
|
||||||
})(this, (function (module, require_sourcemapCodec, require_traceMapping) {
|
})(this, (function (module, require_sourcemapCodec, require_traceMapping) {
|
||||||
"use strict";
|
"use strict";
|
||||||
var __create = Object.create;
|
var __create = Object.create;
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@jridgewell/gen-mapping",
|
"name": "@jridgewell/gen-mapping",
|
||||||
"version": "0.3.12",
|
"version": "0.3.13",
|
||||||
"description": "Generate source maps",
|
"description": "Generate source maps",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"source",
|
"source",
|
||||||
|
|
@ -21,11 +21,7 @@
|
||||||
"types": "./types/gen-mapping.d.mts",
|
"types": "./types/gen-mapping.d.mts",
|
||||||
"default": "./dist/gen-mapping.mjs"
|
"default": "./dist/gen-mapping.mjs"
|
||||||
},
|
},
|
||||||
"require": {
|
"default": {
|
||||||
"types": "./types/gen-mapping.d.cts",
|
|
||||||
"default": "./dist/gen-mapping.umd.js"
|
|
||||||
},
|
|
||||||
"browser": {
|
|
||||||
"types": "./types/gen-mapping.d.cts",
|
"types": "./types/gen-mapping.d.cts",
|
||||||
"default": "./dist/gen-mapping.umd.js"
|
"default": "./dist/gen-mapping.umd.js"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,19 @@
|
||||||
(function (global, factory, m) {
|
(function (global, factory) {
|
||||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(module) :
|
if (typeof exports === 'object' && typeof module !== 'undefined') {
|
||||||
typeof define === 'function' && define.amd ? define(['module'], factory) :
|
factory(module);
|
||||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(m = { exports: {} }), global.sourcemapCodec = 'default' in m.exports ? m.exports.default : m.exports);
|
module.exports = def(module);
|
||||||
|
} else if (typeof define === 'function' && define.amd) {
|
||||||
|
define(['module'], function(mod) {
|
||||||
|
factory.apply(this, arguments);
|
||||||
|
mod.exports = def(mod);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const mod = { exports: {} };
|
||||||
|
factory(mod);
|
||||||
|
global = typeof globalThis !== 'undefined' ? globalThis : global || self;
|
||||||
|
global.sourcemapCodec = def(mod);
|
||||||
|
}
|
||||||
|
function def(m) { return 'default' in m.exports ? m.exports.default : m.exports; }
|
||||||
})(this, (function (module) {
|
})(this, (function (module) {
|
||||||
"use strict";
|
"use strict";
|
||||||
var __defProp = Object.defineProperty;
|
var __defProp = Object.defineProperty;
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@jridgewell/sourcemap-codec",
|
"name": "@jridgewell/sourcemap-codec",
|
||||||
"version": "1.5.4",
|
"version": "1.5.5",
|
||||||
"description": "Encode/decode sourcemap mappings",
|
"description": "Encode/decode sourcemap mappings",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"sourcemap",
|
"sourcemap",
|
||||||
|
|
@ -21,11 +21,7 @@
|
||||||
"types": "./types/sourcemap-codec.d.mts",
|
"types": "./types/sourcemap-codec.d.mts",
|
||||||
"default": "./dist/sourcemap-codec.mjs"
|
"default": "./dist/sourcemap-codec.mjs"
|
||||||
},
|
},
|
||||||
"require": {
|
"default": {
|
||||||
"types": "./types/sourcemap-codec.d.cts",
|
|
||||||
"default": "./dist/sourcemap-codec.umd.js"
|
|
||||||
},
|
|
||||||
"browser": {
|
|
||||||
"types": "./types/sourcemap-codec.d.cts",
|
"types": "./types/sourcemap-codec.d.cts",
|
||||||
"default": "./dist/sourcemap-codec.umd.js"
|
"default": "./dist/sourcemap-codec.umd.js"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,32 @@ function sortComparator(a, b) {
|
||||||
return a[COLUMN] - b[COLUMN];
|
return a[COLUMN] - b[COLUMN];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// src/by-source.ts
|
||||||
|
function buildBySources(decoded, memos) {
|
||||||
|
const sources = memos.map(() => []);
|
||||||
|
for (let i = 0; i < decoded.length; i++) {
|
||||||
|
const line = decoded[i];
|
||||||
|
for (let j = 0; j < line.length; j++) {
|
||||||
|
const seg = line[j];
|
||||||
|
if (seg.length === 1) continue;
|
||||||
|
const sourceIndex2 = seg[SOURCES_INDEX];
|
||||||
|
const sourceLine = seg[SOURCE_LINE];
|
||||||
|
const sourceColumn = seg[SOURCE_COLUMN];
|
||||||
|
const source = sources[sourceIndex2];
|
||||||
|
const segs = source[sourceLine] || (source[sourceLine] = []);
|
||||||
|
segs.push([sourceColumn, i, seg[COLUMN]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let i = 0; i < sources.length; i++) {
|
||||||
|
const source = sources[i];
|
||||||
|
for (let j = 0; j < source.length; j++) {
|
||||||
|
const line = source[j];
|
||||||
|
if (line) line.sort(sortComparator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sources;
|
||||||
|
}
|
||||||
|
|
||||||
// src/binary-search.ts
|
// src/binary-search.ts
|
||||||
var found = false;
|
var found = false;
|
||||||
function binarySearch(haystack, needle, low, high) {
|
function binarySearch(haystack, needle, low, high) {
|
||||||
|
|
@ -117,41 +143,6 @@ function memoizedBinarySearch(haystack, needle, state, key) {
|
||||||
return state.lastIndex = binarySearch(haystack, needle, low, high);
|
return state.lastIndex = binarySearch(haystack, needle, low, high);
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/by-source.ts
|
|
||||||
function buildBySources(decoded, memos) {
|
|
||||||
const sources = memos.map(buildNullArray);
|
|
||||||
for (let i = 0; i < decoded.length; i++) {
|
|
||||||
const line = decoded[i];
|
|
||||||
for (let j = 0; j < line.length; j++) {
|
|
||||||
const seg = line[j];
|
|
||||||
if (seg.length === 1) continue;
|
|
||||||
const sourceIndex2 = seg[SOURCES_INDEX];
|
|
||||||
const sourceLine = seg[SOURCE_LINE];
|
|
||||||
const sourceColumn = seg[SOURCE_COLUMN];
|
|
||||||
const originalSource = sources[sourceIndex2];
|
|
||||||
const originalLine = originalSource[sourceLine] || (originalSource[sourceLine] = []);
|
|
||||||
const memo = memos[sourceIndex2];
|
|
||||||
let index = upperBound(
|
|
||||||
originalLine,
|
|
||||||
sourceColumn,
|
|
||||||
memoizedBinarySearch(originalLine, sourceColumn, memo, sourceLine)
|
|
||||||
);
|
|
||||||
memo.lastIndex = ++index;
|
|
||||||
insert(originalLine, index, [sourceColumn, i, seg[COLUMN]]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sources;
|
|
||||||
}
|
|
||||||
function insert(array, index, value) {
|
|
||||||
for (let i = array.length; i > index; i--) {
|
|
||||||
array[i] = array[i - 1];
|
|
||||||
}
|
|
||||||
array[index] = value;
|
|
||||||
}
|
|
||||||
function buildNullArray() {
|
|
||||||
return { __proto__: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
// src/types.ts
|
// src/types.ts
|
||||||
function parse(map) {
|
function parse(map) {
|
||||||
return typeof map === "string" ? JSON.parse(map) : map;
|
return typeof map === "string" ? JSON.parse(map) : map;
|
||||||
|
|
@ -461,7 +452,7 @@ function sliceGeneratedPositions(segments, memo, line, column, bias) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
function generatedPosition(map, source, line, column, bias, all) {
|
function generatedPosition(map, source, line, column, bias, all) {
|
||||||
var _a;
|
var _a, _b;
|
||||||
line--;
|
line--;
|
||||||
if (line < 0) throw new Error(LINE_GTR_ZERO);
|
if (line < 0) throw new Error(LINE_GTR_ZERO);
|
||||||
if (column < 0) throw new Error(COL_GTR_EQ_ZERO);
|
if (column < 0) throw new Error(COL_GTR_EQ_ZERO);
|
||||||
|
|
@ -469,13 +460,11 @@ function generatedPosition(map, source, line, column, bias, all) {
|
||||||
let sourceIndex2 = sources.indexOf(source);
|
let sourceIndex2 = sources.indexOf(source);
|
||||||
if (sourceIndex2 === -1) sourceIndex2 = resolvedSources.indexOf(source);
|
if (sourceIndex2 === -1) sourceIndex2 = resolvedSources.indexOf(source);
|
||||||
if (sourceIndex2 === -1) return all ? [] : GMapping(null, null);
|
if (sourceIndex2 === -1) return all ? [] : GMapping(null, null);
|
||||||
const generated = (_a = cast(map))._bySources || (_a._bySources = buildBySources(
|
const bySourceMemos = (_a = cast(map))._bySourceMemos || (_a._bySourceMemos = sources.map(memoizedState));
|
||||||
decodedMappings(map),
|
const generated = (_b = cast(map))._bySources || (_b._bySources = buildBySources(decodedMappings(map), bySourceMemos));
|
||||||
cast(map)._bySourceMemos = sources.map(memoizedState)
|
|
||||||
));
|
|
||||||
const segments = generated[sourceIndex2][line];
|
const segments = generated[sourceIndex2][line];
|
||||||
if (segments == null) return all ? [] : GMapping(null, null);
|
if (segments == null) return all ? [] : GMapping(null, null);
|
||||||
const memo = cast(map)._bySourceMemos[sourceIndex2];
|
const memo = bySourceMemos[sourceIndex2];
|
||||||
if (all) return sliceGeneratedPositions(segments, memo, line, column, bias);
|
if (all) return sliceGeneratedPositions(segments, memo, line, column, bias);
|
||||||
const index = traceSegmentInternal(segments, memo, line, column, bias);
|
const index = traceSegmentInternal(segments, memo, line, column, bias);
|
||||||
if (index === -1) return GMapping(null, null);
|
if (index === -1) return GMapping(null, null);
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,7 +1,19 @@
|
||||||
(function (global, factory, m) {
|
(function (global, factory) {
|
||||||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(module, require('@jridgewell/resolve-uri'), require('@jridgewell/sourcemap-codec')) :
|
if (typeof exports === 'object' && typeof module !== 'undefined') {
|
||||||
typeof define === 'function' && define.amd ? define(['module', '@jridgewell/resolve-uri', '@jridgewell/sourcemap-codec'], factory) :
|
factory(module, require('@jridgewell/resolve-uri'), require('@jridgewell/sourcemap-codec'));
|
||||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(m = { exports: {} }, global.resolveURI, global.sourcemapCodec), global.traceMapping = 'default' in m.exports ? m.exports.default : m.exports);
|
module.exports = def(module);
|
||||||
|
} else if (typeof define === 'function' && define.amd) {
|
||||||
|
define(['module', '@jridgewell/resolve-uri', '@jridgewell/sourcemap-codec'], function(mod) {
|
||||||
|
factory.apply(this, arguments);
|
||||||
|
mod.exports = def(mod);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const mod = { exports: {} };
|
||||||
|
factory(mod, global.resolveURI, global.sourcemapCodec);
|
||||||
|
global = typeof globalThis !== 'undefined' ? globalThis : global || self;
|
||||||
|
global.traceMapping = def(mod);
|
||||||
|
}
|
||||||
|
function def(m) { return 'default' in m.exports ? m.exports.default : m.exports; }
|
||||||
})(this, (function (module, require_resolveURI, require_sourcemapCodec) {
|
})(this, (function (module, require_resolveURI, require_sourcemapCodec) {
|
||||||
"use strict";
|
"use strict";
|
||||||
var __create = Object.create;
|
var __create = Object.create;
|
||||||
|
|
@ -131,6 +143,32 @@ function sortComparator(a, b) {
|
||||||
return a[COLUMN] - b[COLUMN];
|
return a[COLUMN] - b[COLUMN];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// src/by-source.ts
|
||||||
|
function buildBySources(decoded, memos) {
|
||||||
|
const sources = memos.map(() => []);
|
||||||
|
for (let i = 0; i < decoded.length; i++) {
|
||||||
|
const line = decoded[i];
|
||||||
|
for (let j = 0; j < line.length; j++) {
|
||||||
|
const seg = line[j];
|
||||||
|
if (seg.length === 1) continue;
|
||||||
|
const sourceIndex2 = seg[SOURCES_INDEX];
|
||||||
|
const sourceLine = seg[SOURCE_LINE];
|
||||||
|
const sourceColumn = seg[SOURCE_COLUMN];
|
||||||
|
const source = sources[sourceIndex2];
|
||||||
|
const segs = source[sourceLine] || (source[sourceLine] = []);
|
||||||
|
segs.push([sourceColumn, i, seg[COLUMN]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let i = 0; i < sources.length; i++) {
|
||||||
|
const source = sources[i];
|
||||||
|
for (let j = 0; j < source.length; j++) {
|
||||||
|
const line = source[j];
|
||||||
|
if (line) line.sort(sortComparator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sources;
|
||||||
|
}
|
||||||
|
|
||||||
// src/binary-search.ts
|
// src/binary-search.ts
|
||||||
var found = false;
|
var found = false;
|
||||||
function binarySearch(haystack, needle, low, high) {
|
function binarySearch(haystack, needle, low, high) {
|
||||||
|
|
@ -189,41 +227,6 @@ function memoizedBinarySearch(haystack, needle, state, key) {
|
||||||
return state.lastIndex = binarySearch(haystack, needle, low, high);
|
return state.lastIndex = binarySearch(haystack, needle, low, high);
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/by-source.ts
|
|
||||||
function buildBySources(decoded, memos) {
|
|
||||||
const sources = memos.map(buildNullArray);
|
|
||||||
for (let i = 0; i < decoded.length; i++) {
|
|
||||||
const line = decoded[i];
|
|
||||||
for (let j = 0; j < line.length; j++) {
|
|
||||||
const seg = line[j];
|
|
||||||
if (seg.length === 1) continue;
|
|
||||||
const sourceIndex2 = seg[SOURCES_INDEX];
|
|
||||||
const sourceLine = seg[SOURCE_LINE];
|
|
||||||
const sourceColumn = seg[SOURCE_COLUMN];
|
|
||||||
const originalSource = sources[sourceIndex2];
|
|
||||||
const originalLine = originalSource[sourceLine] || (originalSource[sourceLine] = []);
|
|
||||||
const memo = memos[sourceIndex2];
|
|
||||||
let index = upperBound(
|
|
||||||
originalLine,
|
|
||||||
sourceColumn,
|
|
||||||
memoizedBinarySearch(originalLine, sourceColumn, memo, sourceLine)
|
|
||||||
);
|
|
||||||
memo.lastIndex = ++index;
|
|
||||||
insert(originalLine, index, [sourceColumn, i, seg[COLUMN]]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sources;
|
|
||||||
}
|
|
||||||
function insert(array, index, value) {
|
|
||||||
for (let i = array.length; i > index; i--) {
|
|
||||||
array[i] = array[i - 1];
|
|
||||||
}
|
|
||||||
array[index] = value;
|
|
||||||
}
|
|
||||||
function buildNullArray() {
|
|
||||||
return { __proto__: null };
|
|
||||||
}
|
|
||||||
|
|
||||||
// src/types.ts
|
// src/types.ts
|
||||||
function parse(map) {
|
function parse(map) {
|
||||||
return typeof map === "string" ? JSON.parse(map) : map;
|
return typeof map === "string" ? JSON.parse(map) : map;
|
||||||
|
|
@ -533,7 +536,7 @@ function sliceGeneratedPositions(segments, memo, line, column, bias) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
function generatedPosition(map, source, line, column, bias, all) {
|
function generatedPosition(map, source, line, column, bias, all) {
|
||||||
var _a;
|
var _a, _b;
|
||||||
line--;
|
line--;
|
||||||
if (line < 0) throw new Error(LINE_GTR_ZERO);
|
if (line < 0) throw new Error(LINE_GTR_ZERO);
|
||||||
if (column < 0) throw new Error(COL_GTR_EQ_ZERO);
|
if (column < 0) throw new Error(COL_GTR_EQ_ZERO);
|
||||||
|
|
@ -541,13 +544,11 @@ function generatedPosition(map, source, line, column, bias, all) {
|
||||||
let sourceIndex2 = sources.indexOf(source);
|
let sourceIndex2 = sources.indexOf(source);
|
||||||
if (sourceIndex2 === -1) sourceIndex2 = resolvedSources.indexOf(source);
|
if (sourceIndex2 === -1) sourceIndex2 = resolvedSources.indexOf(source);
|
||||||
if (sourceIndex2 === -1) return all ? [] : GMapping(null, null);
|
if (sourceIndex2 === -1) return all ? [] : GMapping(null, null);
|
||||||
const generated = (_a = cast(map))._bySources || (_a._bySources = buildBySources(
|
const bySourceMemos = (_a = cast(map))._bySourceMemos || (_a._bySourceMemos = sources.map(memoizedState));
|
||||||
decodedMappings(map),
|
const generated = (_b = cast(map))._bySources || (_b._bySources = buildBySources(decodedMappings(map), bySourceMemos));
|
||||||
cast(map)._bySourceMemos = sources.map(memoizedState)
|
|
||||||
));
|
|
||||||
const segments = generated[sourceIndex2][line];
|
const segments = generated[sourceIndex2][line];
|
||||||
if (segments == null) return all ? [] : GMapping(null, null);
|
if (segments == null) return all ? [] : GMapping(null, null);
|
||||||
const memo = cast(map)._bySourceMemos[sourceIndex2];
|
const memo = bySourceMemos[sourceIndex2];
|
||||||
if (all) return sliceGeneratedPositions(segments, memo, line, column, bias);
|
if (all) return sliceGeneratedPositions(segments, memo, line, column, bias);
|
||||||
const index = traceSegmentInternal(segments, memo, line, column, bias);
|
const index = traceSegmentInternal(segments, memo, line, column, bias);
|
||||||
if (index === -1) return GMapping(null, null);
|
if (index === -1) return GMapping(null, null);
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@jridgewell/trace-mapping",
|
"name": "@jridgewell/trace-mapping",
|
||||||
"version": "0.3.29",
|
"version": "0.3.31",
|
||||||
"description": "Trace the original position through a source map",
|
"description": "Trace the original position through a source map",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"source",
|
"source",
|
||||||
|
|
@ -21,11 +21,7 @@
|
||||||
"types": "./types/trace-mapping.d.mts",
|
"types": "./types/trace-mapping.d.mts",
|
||||||
"default": "./dist/trace-mapping.mjs"
|
"default": "./dist/trace-mapping.mjs"
|
||||||
},
|
},
|
||||||
"require": {
|
"default": {
|
||||||
"types": "./types/trace-mapping.d.cts",
|
|
||||||
"default": "./dist/trace-mapping.umd.js"
|
|
||||||
},
|
|
||||||
"browser": {
|
|
||||||
"types": "./types/trace-mapping.d.cts",
|
"types": "./types/trace-mapping.d.cts",
|
||||||
"default": "./dist/trace-mapping.umd.js"
|
"default": "./dist/trace-mapping.umd.js"
|
||||||
}
|
}
|
||||||
|
|
@ -37,7 +33,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"benchmark": "run-s build:code benchmark:*",
|
"benchmark": "run-s build:code benchmark:*",
|
||||||
"benchmark:install": "cd benchmark && npm install",
|
"benchmark:install": "cd benchmark && npm install",
|
||||||
"benchmark:only": "node --expose-gc benchmark/index.js",
|
"benchmark:only": "node --expose-gc benchmark/index.mjs",
|
||||||
"build": "run-s -n build:code build:types",
|
"build": "run-s -n build:code build:types",
|
||||||
"build:code": "node ../../esbuild.mjs trace-mapping.ts",
|
"build:code": "node ../../esbuild.mjs trace-mapping.ts",
|
||||||
"build:types": "run-s build:types:force build:types:emit build:types:mts",
|
"build:types": "run-s build:types:force build:types:emit build:types:mts",
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,17 @@
|
||||||
import { COLUMN, SOURCES_INDEX, SOURCE_LINE, SOURCE_COLUMN } from './sourcemap-segment';
|
import { COLUMN, SOURCES_INDEX, SOURCE_LINE, SOURCE_COLUMN } from './sourcemap-segment';
|
||||||
import { memoizedBinarySearch, upperBound } from './binary-search';
|
import { sortComparator } from './sort';
|
||||||
|
|
||||||
import type { ReverseSegment, SourceMapSegment } from './sourcemap-segment';
|
import type { ReverseSegment, SourceMapSegment } from './sourcemap-segment';
|
||||||
import type { MemoState } from './binary-search';
|
|
||||||
|
|
||||||
export type Source = {
|
export type Source = ReverseSegment[][];
|
||||||
__proto__: null;
|
|
||||||
[line: number]: Exclude<ReverseSegment, [number]>[];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Rebuilds the original source files, with mappings that are ordered by source line/column instead
|
// Rebuilds the original source files, with mappings that are ordered by source line/column instead
|
||||||
// of generated line/column.
|
// of generated line/column.
|
||||||
export default function buildBySources(
|
export default function buildBySources(
|
||||||
decoded: readonly SourceMapSegment[][],
|
decoded: readonly SourceMapSegment[][],
|
||||||
memos: MemoState[],
|
memos: unknown[],
|
||||||
): Source[] {
|
): Source[] {
|
||||||
const sources: Source[] = memos.map(buildNullArray);
|
const sources: Source[] = memos.map(() => []);
|
||||||
|
|
||||||
for (let i = 0; i < decoded.length; i++) {
|
for (let i = 0; i < decoded.length; i++) {
|
||||||
const line = decoded[i];
|
const line = decoded[i];
|
||||||
|
|
@ -26,40 +22,20 @@ export default function buildBySources(
|
||||||
const sourceIndex = seg[SOURCES_INDEX];
|
const sourceIndex = seg[SOURCES_INDEX];
|
||||||
const sourceLine = seg[SOURCE_LINE];
|
const sourceLine = seg[SOURCE_LINE];
|
||||||
const sourceColumn = seg[SOURCE_COLUMN];
|
const sourceColumn = seg[SOURCE_COLUMN];
|
||||||
const originalSource = sources[sourceIndex];
|
|
||||||
const originalLine = (originalSource[sourceLine] ||= []);
|
|
||||||
const memo = memos[sourceIndex];
|
|
||||||
|
|
||||||
// The binary search either found a match, or it found the left-index just before where the
|
const source = sources[sourceIndex];
|
||||||
// segment should go. Either way, we want to insert after that. And there may be multiple
|
const segs = (source[sourceLine] ||= []);
|
||||||
// generated segments associated with an original location, so there may need to move several
|
segs.push([sourceColumn, i, seg[COLUMN]]);
|
||||||
// indexes before we find where we need to insert.
|
}
|
||||||
let index = upperBound(
|
}
|
||||||
originalLine,
|
|
||||||
sourceColumn,
|
|
||||||
memoizedBinarySearch(originalLine, sourceColumn, memo, sourceLine),
|
|
||||||
);
|
|
||||||
|
|
||||||
memo.lastIndex = ++index;
|
for (let i = 0; i < sources.length; i++) {
|
||||||
insert(originalLine, index, [sourceColumn, i, seg[COLUMN]]);
|
const source = sources[i];
|
||||||
|
for (let j = 0; j < source.length; j++) {
|
||||||
|
const line = source[j];
|
||||||
|
if (line) line.sort(sortComparator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sources;
|
return sources;
|
||||||
}
|
}
|
||||||
|
|
||||||
function insert<T>(array: T[], index: number, value: T) {
|
|
||||||
for (let i = array.length; i > index; i--) {
|
|
||||||
array[i] = array[i - 1];
|
|
||||||
}
|
|
||||||
array[index] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Null arrays allow us to use ordered index keys without actually allocating contiguous memory like
|
|
||||||
// a real array. We use a null-prototype object to avoid prototype pollution and deoptimizations.
|
|
||||||
// Numeric properties on objects are magically sorted in ascending order by the engine regardless of
|
|
||||||
// the insertion order. So, by setting any numeric keys, even out of order, we'll get ascending
|
|
||||||
// order when iterating with for-in.
|
|
||||||
function buildNullArray<T extends { __proto__: null }>(): T {
|
|
||||||
return { __proto__: null } as T;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { COLUMN } from './sourcemap-segment';
|
import { COLUMN } from './sourcemap-segment';
|
||||||
|
|
||||||
import type { SourceMapSegment } from './sourcemap-segment';
|
import type { ReverseSegment, SourceMapSegment } from './sourcemap-segment';
|
||||||
|
|
||||||
export default function maybeSort(
|
export default function maybeSort(
|
||||||
mappings: SourceMapSegment[][],
|
mappings: SourceMapSegment[][],
|
||||||
|
|
@ -40,6 +40,6 @@ function sortSegments(line: SourceMapSegment[], owned: boolean): SourceMapSegmen
|
||||||
return line.sort(sortComparator);
|
return line.sort(sortComparator);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortComparator(a: SourceMapSegment, b: SourceMapSegment): number {
|
export function sortComparator<T extends SourceMapSegment | ReverseSegment>(a: T, b: T): number {
|
||||||
return a[COLUMN] - b[COLUMN];
|
return a[COLUMN] - b[COLUMN];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -484,15 +484,13 @@ function generatedPosition(
|
||||||
if (sourceIndex === -1) sourceIndex = resolvedSources.indexOf(source);
|
if (sourceIndex === -1) sourceIndex = resolvedSources.indexOf(source);
|
||||||
if (sourceIndex === -1) return all ? [] : GMapping(null, null);
|
if (sourceIndex === -1) return all ? [] : GMapping(null, null);
|
||||||
|
|
||||||
const generated = (cast(map)._bySources ||= buildBySources(
|
const bySourceMemos = (cast(map)._bySourceMemos ||= sources.map(memoizedState));
|
||||||
decodedMappings(map),
|
const generated = (cast(map)._bySources ||= buildBySources(decodedMappings(map), bySourceMemos));
|
||||||
(cast(map)._bySourceMemos = sources.map(memoizedState)),
|
|
||||||
));
|
|
||||||
|
|
||||||
const segments = generated[sourceIndex][line];
|
const segments = generated[sourceIndex][line];
|
||||||
if (segments == null) return all ? [] : GMapping(null, null);
|
if (segments == null) return all ? [] : GMapping(null, null);
|
||||||
|
|
||||||
const memo = cast(map)._bySourceMemos![sourceIndex];
|
const memo = bySourceMemos[sourceIndex];
|
||||||
|
|
||||||
if (all) return sliceGeneratedPositions(segments, memo, line, column, bias);
|
if (all) return sliceGeneratedPositions(segments, memo, line, column, bias);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,4 @@
|
||||||
import type { ReverseSegment, SourceMapSegment } from './sourcemap-segment.cts';
|
import type { ReverseSegment, SourceMapSegment } from './sourcemap-segment.cts';
|
||||||
import type { MemoState } from './binary-search.cts';
|
export type Source = ReverseSegment[][];
|
||||||
export type Source = {
|
export = function buildBySources(decoded: readonly SourceMapSegment[][], memos: unknown[]): Source[];
|
||||||
__proto__: null;
|
|
||||||
[line: number]: Exclude<ReverseSegment, [number]>[];
|
|
||||||
};
|
|
||||||
export = function buildBySources(decoded: readonly SourceMapSegment[][], memos: MemoState[]): Source[];
|
|
||||||
//# sourceMappingURL=by-source.d.ts.map
|
//# sourceMappingURL=by-source.d.ts.map
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"version":3,"file":"by-source.d.ts","sourceRoot":"","sources":["../src/by-source.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,MAAM,MAAM,GAAG;IACnB,SAAS,EAAE,IAAI,CAAC;IAChB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;CACrD,CAAC;AAIF,MAAM,CAAC,OAAO,UAAU,cAAc,CACpC,OAAO,EAAE,SAAS,gBAAgB,EAAE,EAAE,EACtC,KAAK,EAAE,SAAS,EAAE,GACjB,MAAM,EAAE,CAgCV"}
|
{"version":3,"file":"by-source.d.ts","sourceRoot":"","sources":["../src/by-source.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5E,MAAM,MAAM,MAAM,GAAG,cAAc,EAAE,EAAE,CAAC;AAIxC,MAAM,CAAC,OAAO,UAAU,cAAc,CACpC,OAAO,EAAE,SAAS,gBAAgB,EAAE,EAAE,EACtC,KAAK,EAAE,OAAO,EAAE,GACf,MAAM,EAAE,CA4BV"}
|
||||||
|
|
@ -1,8 +1,4 @@
|
||||||
import type { ReverseSegment, SourceMapSegment } from './sourcemap-segment.mts';
|
import type { ReverseSegment, SourceMapSegment } from './sourcemap-segment.mts';
|
||||||
import type { MemoState } from './binary-search.mts';
|
export type Source = ReverseSegment[][];
|
||||||
export type Source = {
|
export default function buildBySources(decoded: readonly SourceMapSegment[][], memos: unknown[]): Source[];
|
||||||
__proto__: null;
|
|
||||||
[line: number]: Exclude<ReverseSegment, [number]>[];
|
|
||||||
};
|
|
||||||
export default function buildBySources(decoded: readonly SourceMapSegment[][], memos: MemoState[]): Source[];
|
|
||||||
//# sourceMappingURL=by-source.d.ts.map
|
//# sourceMappingURL=by-source.d.ts.map
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"version":3,"file":"by-source.d.ts","sourceRoot":"","sources":["../src/by-source.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,MAAM,MAAM,GAAG;IACnB,SAAS,EAAE,IAAI,CAAC;IAChB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;CACrD,CAAC;AAIF,MAAM,CAAC,OAAO,UAAU,cAAc,CACpC,OAAO,EAAE,SAAS,gBAAgB,EAAE,EAAE,EACtC,KAAK,EAAE,SAAS,EAAE,GACjB,MAAM,EAAE,CAgCV"}
|
{"version":3,"file":"by-source.d.ts","sourceRoot":"","sources":["../src/by-source.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5E,MAAM,MAAM,MAAM,GAAG,cAAc,EAAE,EAAE,CAAC;AAIxC,MAAM,CAAC,OAAO,UAAU,cAAc,CACpC,OAAO,EAAE,SAAS,gBAAgB,EAAE,EAAE,EACtC,KAAK,EAAE,OAAO,EAAE,GACf,MAAM,EAAE,CA4BV"}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
import type { SourceMapSegment } from './sourcemap-segment.cts';
|
import type { ReverseSegment, SourceMapSegment } from './sourcemap-segment.cts';
|
||||||
export = function maybeSort(mappings: SourceMapSegment[][], owned: boolean): SourceMapSegment[][];
|
export = function maybeSort(mappings: SourceMapSegment[][], owned: boolean): SourceMapSegment[][];
|
||||||
|
export declare function sortComparator<T extends SourceMapSegment | ReverseSegment>(a: T, b: T): number;
|
||||||
//# sourceMappingURL=sort.d.ts.map
|
//# sourceMappingURL=sort.d.ts.map
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"version":3,"file":"sort.d.ts","sourceRoot":"","sources":["../src/sort.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,QAAQ,EAAE,gBAAgB,EAAE,EAAE,EAC9B,KAAK,EAAE,OAAO,GACb,gBAAgB,EAAE,EAAE,CAYtB"}
|
{"version":3,"file":"sort.d.ts","sourceRoot":"","sources":["../src/sort.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5E,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,QAAQ,EAAE,gBAAgB,EAAE,EAAE,EAC9B,KAAK,EAAE,OAAO,GACb,gBAAgB,EAAE,EAAE,CAYtB;AAuBD,wBAAgB,cAAc,CAAC,CAAC,SAAS,gBAAgB,GAAG,cAAc,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,MAAM,CAE9F"}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
import type { SourceMapSegment } from './sourcemap-segment.mts';
|
import type { ReverseSegment, SourceMapSegment } from './sourcemap-segment.mts';
|
||||||
export default function maybeSort(mappings: SourceMapSegment[][], owned: boolean): SourceMapSegment[][];
|
export default function maybeSort(mappings: SourceMapSegment[][], owned: boolean): SourceMapSegment[][];
|
||||||
|
export declare function sortComparator<T extends SourceMapSegment | ReverseSegment>(a: T, b: T): number;
|
||||||
//# sourceMappingURL=sort.d.ts.map
|
//# sourceMappingURL=sort.d.ts.map
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"version":3,"file":"sort.d.ts","sourceRoot":"","sources":["../src/sort.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,QAAQ,EAAE,gBAAgB,EAAE,EAAE,EAC9B,KAAK,EAAE,OAAO,GACb,gBAAgB,EAAE,EAAE,CAYtB"}
|
{"version":3,"file":"sort.d.ts","sourceRoot":"","sources":["../src/sort.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5E,MAAM,CAAC,OAAO,UAAU,SAAS,CAC/B,QAAQ,EAAE,gBAAgB,EAAE,EAAE,EAC9B,KAAK,EAAE,OAAO,GACb,gBAAgB,EAAE,EAAE,CAYtB;AAuBD,wBAAgB,cAAc,CAAC,CAAC,SAAS,gBAAgB,GAAG,cAAc,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,MAAM,CAE9F"}
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2017-present Devon Govett
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
This is the win32-x64 build of @parcel/watcher. See https://github.com/parcel-bundler/watcher for details.
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@parcel/watcher-win32-x64",
|
|
||||||
"version": "2.5.1",
|
|
||||||
"main": "watcher.node",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/parcel-bundler/watcher.git"
|
|
||||||
},
|
|
||||||
"description": "A native C++ Node module for querying and subscribing to filesystem events. Used by Parcel 2.",
|
|
||||||
"license": "MIT",
|
|
||||||
"publishConfig": {
|
|
||||||
"access": "public"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/parcel"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"watcher.node"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10.0.0"
|
|
||||||
},
|
|
||||||
"os": [
|
|
||||||
"win32"
|
|
||||||
],
|
|
||||||
"cpu": [
|
|
||||||
"x64"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
|
@ -103,7 +103,7 @@ You can specify the exact backend you wish to use by passing the `backend` optio
|
||||||
|
|
||||||
All of the APIs in `@parcel/watcher` support the following options, which are passed as an object as the last function argument.
|
All of the APIs in `@parcel/watcher` support the following options, which are passed as an object as the last function argument.
|
||||||
|
|
||||||
- `ignore` - an array of paths or glob patterns to ignore. uses [`is-glob`](https://github.com/micromatch/is-glob) to distinguish paths from globs. glob patterns are parsed with [`micromatch`](https://github.com/micromatch/micromatch) (see [features](https://github.com/micromatch/micromatch#matching-features)).
|
- `ignore` - an array of paths or glob patterns to ignore. uses [`is-glob`](https://github.com/micromatch/is-glob) to distinguish paths from globs. glob patterns are parsed with [`picomatch`](https://github.com/micromatch/picomatch) (see [features](https://github.com/micromatch/picomatch#globbing-features)).
|
||||||
- paths can be relative or absolute and can either be files or directories. No events will be emitted about these files or directories or their children.
|
- paths can be relative or absolute and can either be files or directories. No events will be emitted about these files or directories or their children.
|
||||||
- glob patterns match on relative paths from the root that is watched. No events will be emitted for matching paths.
|
- glob patterns match on relative paths from the root that is watched. No events will be emitted for matching paths.
|
||||||
- `backend` - the name of an explicitly chosen backend to use. Allowed options are `"fs-events"`, `"watchman"`, `"inotify"`, `"kqueue"`, `"windows"`, or `"brute-force"` (only for querying). If the specified backend is not available on the current platform, the default backend will be used instead.
|
- `backend` - the name of an explicitly chosen backend to use. Allowed options are `"fs-events"`, `"watchman"`, `"inotify"`, `"kqueue"`, `"windows"`, or `"brute-force"` (only for querying). If the specified backend is not available on the current platform, the default backend will be used instead.
|
||||||
|
|
@ -129,6 +129,7 @@ subscribe(/* ... */);
|
||||||
- [Gatsby Cloud](https://twitter.com/chatsidhartha/status/1435647412828196867)
|
- [Gatsby Cloud](https://twitter.com/chatsidhartha/status/1435647412828196867)
|
||||||
- [Nx](https://nx.dev)
|
- [Nx](https://nx.dev)
|
||||||
- [Nuxt](https://nuxt.com)
|
- [Nuxt](https://nuxt.com)
|
||||||
|
- [Meteor](https://github.com/meteor/meteor)
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
"include_dirs" : ["<!(node -p \"require('node-addon-api').include_dir\")"],
|
"include_dirs" : ["<!(node -p \"require('node-addon-api').include_dir\")"],
|
||||||
'cflags!': [ '-fno-exceptions', '-std=c++17' ],
|
'cflags!': [ '-fno-exceptions', '-std=c++17' ],
|
||||||
'cflags_cc!': [ '-fno-exceptions', '-std=c++17' ],
|
'cflags_cc!': [ '-fno-exceptions', '-std=c++17' ],
|
||||||
|
'cflags': [ '-fstack-protector-strong' ],
|
||||||
"conditions": [
|
"conditions": [
|
||||||
['OS=="mac"', {
|
['OS=="mac"', {
|
||||||
"sources": [
|
"sources": [
|
||||||
|
|
@ -65,7 +66,22 @@
|
||||||
"msvs_settings": {
|
"msvs_settings": {
|
||||||
"VCCLCompilerTool": {
|
"VCCLCompilerTool": {
|
||||||
"ExceptionHandling": 1, # /EHsc
|
"ExceptionHandling": 1, # /EHsc
|
||||||
"AdditionalOptions": ['-std:c++17']
|
"AdditionalOptions": [
|
||||||
|
"-std:c++17",
|
||||||
|
"/guard:cf",
|
||||||
|
"/W3",
|
||||||
|
"/we4146",
|
||||||
|
"/w34244",
|
||||||
|
"/we4267",
|
||||||
|
"/sdl",
|
||||||
|
"/ZH:SHA_256"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"VCLinkerTool": {
|
||||||
|
"AdditionalOptions": [
|
||||||
|
"/DYNAMICBASE",
|
||||||
|
"/guard:cf"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ const {createWrapper} = require('./wrapper');
|
||||||
|
|
||||||
let name = `@parcel/watcher-${process.platform}-${process.arch}`;
|
let name = `@parcel/watcher-${process.platform}-${process.arch}`;
|
||||||
if (process.platform === 'linux') {
|
if (process.platform === 'linux') {
|
||||||
const { MUSL, family } = require('detect-libc');
|
const { MUSL, familySync } = require('detect-libc');
|
||||||
|
const family = familySync();
|
||||||
if (family === MUSL) {
|
if (family === MUSL) {
|
||||||
name += '-musl';
|
name += '-musl';
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@parcel/watcher",
|
"name": "@parcel/watcher",
|
||||||
"version": "2.5.1",
|
"version": "2.5.6",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"types": "index.d.ts",
|
"types": "index.d.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
@ -50,10 +50,10 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"detect-libc": "^1.0.3",
|
"detect-libc": "^2.0.3",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
"micromatch": "^4.0.5",
|
"node-addon-api": "^7.0.0",
|
||||||
"node-addon-api": "^7.0.0"
|
"picomatch": "^4.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"esbuild": "^0.19.8",
|
"esbuild": "^0.19.8",
|
||||||
|
|
@ -71,18 +71,18 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@parcel/watcher-darwin-x64": "2.5.1",
|
"@parcel/watcher-darwin-x64": "2.5.6",
|
||||||
"@parcel/watcher-darwin-arm64": "2.5.1",
|
"@parcel/watcher-darwin-arm64": "2.5.6",
|
||||||
"@parcel/watcher-win32-x64": "2.5.1",
|
"@parcel/watcher-win32-x64": "2.5.6",
|
||||||
"@parcel/watcher-win32-arm64": "2.5.1",
|
"@parcel/watcher-win32-arm64": "2.5.6",
|
||||||
"@parcel/watcher-win32-ia32": "2.5.1",
|
"@parcel/watcher-win32-ia32": "2.5.6",
|
||||||
"@parcel/watcher-linux-x64-glibc": "2.5.1",
|
"@parcel/watcher-linux-x64-glibc": "2.5.6",
|
||||||
"@parcel/watcher-linux-x64-musl": "2.5.1",
|
"@parcel/watcher-linux-x64-musl": "2.5.6",
|
||||||
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
|
"@parcel/watcher-linux-arm64-glibc": "2.5.6",
|
||||||
"@parcel/watcher-linux-arm64-musl": "2.5.1",
|
"@parcel/watcher-linux-arm64-musl": "2.5.6",
|
||||||
"@parcel/watcher-linux-arm-glibc": "2.5.1",
|
"@parcel/watcher-linux-arm-glibc": "2.5.6",
|
||||||
"@parcel/watcher-linux-arm-musl": "2.5.1",
|
"@parcel/watcher-linux-arm-musl": "2.5.6",
|
||||||
"@parcel/watcher-android-arm64": "2.5.1",
|
"@parcel/watcher-android-arm64": "2.5.6",
|
||||||
"@parcel/watcher-freebsd-x64": "2.5.1"
|
"@parcel/watcher-freebsd-x64": "2.5.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,11 @@
|
||||||
#include "Backend.hh"
|
#include "Backend.hh"
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
static std::unordered_map<std::string, std::shared_ptr<Backend>> sharedBackends;
|
static std::unordered_map<std::string, std::shared_ptr<Backend>>& getSharedBackends() {
|
||||||
|
static std::unordered_map<std::string, std::shared_ptr<Backend>>* sharedBackends =
|
||||||
|
new std::unordered_map<std::string, std::shared_ptr<Backend>>();
|
||||||
|
return *sharedBackends;
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<Backend> getBackend(std::string backend) {
|
std::shared_ptr<Backend> getBackend(std::string backend) {
|
||||||
// Use FSEvents on macOS by default.
|
// Use FSEvents on macOS by default.
|
||||||
|
|
@ -65,8 +69,8 @@ std::shared_ptr<Backend> getBackend(std::string backend) {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Backend> Backend::getShared(std::string backend) {
|
std::shared_ptr<Backend> Backend::getShared(std::string backend) {
|
||||||
auto found = sharedBackends.find(backend);
|
auto found = getSharedBackends().find(backend);
|
||||||
if (found != sharedBackends.end()) {
|
if (found != getSharedBackends().end()) {
|
||||||
return found->second;
|
return found->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,21 +80,21 @@ std::shared_ptr<Backend> Backend::getShared(std::string backend) {
|
||||||
}
|
}
|
||||||
|
|
||||||
result->run();
|
result->run();
|
||||||
sharedBackends.emplace(backend, result);
|
getSharedBackends().emplace(backend, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeShared(Backend *backend) {
|
void removeShared(Backend *backend) {
|
||||||
for (auto it = sharedBackends.begin(); it != sharedBackends.end(); it++) {
|
for (auto it = getSharedBackends().begin(); it != getSharedBackends().end(); it++) {
|
||||||
if (it->second.get() == backend) {
|
if (it->second.get() == backend) {
|
||||||
sharedBackends.erase(it);
|
getSharedBackends().erase(it);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Free up memory.
|
// Free up memory.
|
||||||
if (sharedBackends.size() == 0) {
|
if (getSharedBackends().size() == 0) {
|
||||||
sharedBackends.rehash(0);
|
getSharedBackends().rehash(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,7 +149,7 @@ void Backend::watch(WatcherRef watcher) {
|
||||||
try {
|
try {
|
||||||
this->subscribe(watcher);
|
this->subscribe(watcher);
|
||||||
mSubscriptions.insert(watcher);
|
mSubscriptions.insert(watcher);
|
||||||
} catch (std::exception &err) {
|
} catch (std::exception&) {
|
||||||
unref();
|
unref();
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue