update: driver upst

main
muamars 2026-02-18 10:47:53 +07:00
parent ddd831e390
commit a6584554f2
634 changed files with 12864 additions and 34463 deletions

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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 });
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}

View File

@ -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");
}
}
}
}

View File

@ -2,6 +2,7 @@ var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddHttpClient();
var app = builder.Build();

View File

@ -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

View File

@ -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

View File

@ -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, '&quot;');
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, '&quot;');
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>

View File

@ -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>

View File

@ -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>

View File

@ -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: "&copy; 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>

View File

@ -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>

View File

@ -0,0 +1,6 @@
@{
ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>
<p>Use this page to detail your site's privacy policy.</p>

View File

@ -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>
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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;
}

View File

@ -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>

View File

@ -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

View File

@ -8,5 +8,11 @@
"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"
},
"OpenRouter": {
"OCRkey": "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"
},
"Mapbox": {
"Apikey": "pk.eyJ1IjoibWFyc3pheW4iLCJhIjoiY21sajUyMDd0MDllcjNncGdna3NzeTR4ZyJ9.Wvbh5J94j9sF_8KNPp9FYQ"
},
"AllowedHosts": "*"
}

16
node_modules/.bin/detect-libc generated vendored
View File

@ -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

17
node_modules/.bin/detect-libc.cmd generated vendored
View File

@ -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" %*

28
node_modules/.bin/detect-libc.ps1 generated vendored
View File

@ -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

16
node_modules/.bin/jiti generated vendored
View File

@ -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

1
node_modules/.bin/jiti generated vendored 120000
View File

@ -0,0 +1 @@
../jiti/lib/jiti-cli.mjs

17
node_modules/.bin/jiti.cmd generated vendored
View File

@ -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" %*

28
node_modules/.bin/jiti.ps1 generated vendored
View File

@ -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

16
node_modules/.bin/mkdirp generated vendored
View File

@ -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

17
node_modules/.bin/mkdirp.cmd generated vendored
View File

@ -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" %*

28
node_modules/.bin/mkdirp.ps1 generated vendored
View File

@ -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

16
node_modules/.bin/tailwindcss generated vendored
View File

@ -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

1
node_modules/.bin/tailwindcss generated vendored 120000
View File

@ -0,0 +1 @@
../@tailwindcss/cli/dist/index.mjs

17
node_modules/.bin/tailwindcss.cmd generated vendored
View File

@ -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" %*

28
node_modules/.bin/tailwindcss.ps1 generated vendored
View File

@ -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

417
node_modules/.package-lock.json generated vendored
View File

@ -3,41 +3,26 @@
"lockfileVersion": 3,
"requires": true,
"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": {
"version": "0.3.12",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
"integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
"@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": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
@ -48,15 +33,15 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
"integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.29",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
"integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@ -64,16 +49,16 @@
}
},
"node_modules/@parcel/watcher": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
"integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz",
"integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"detect-libc": "^1.0.3",
"detect-libc": "^2.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": {
"node": ">= 10.0.0"
@ -83,32 +68,32 @@
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"@parcel/watcher-android-arm64": "2.5.1",
"@parcel/watcher-darwin-arm64": "2.5.1",
"@parcel/watcher-darwin-x64": "2.5.1",
"@parcel/watcher-freebsd-x64": "2.5.1",
"@parcel/watcher-linux-arm-glibc": "2.5.1",
"@parcel/watcher-linux-arm-musl": "2.5.1",
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
"@parcel/watcher-linux-arm64-musl": "2.5.1",
"@parcel/watcher-linux-x64-glibc": "2.5.1",
"@parcel/watcher-linux-x64-musl": "2.5.1",
"@parcel/watcher-win32-arm64": "2.5.1",
"@parcel/watcher-win32-ia32": "2.5.1",
"@parcel/watcher-win32-x64": "2.5.1"
"@parcel/watcher-android-arm64": "2.5.6",
"@parcel/watcher-darwin-arm64": "2.5.6",
"@parcel/watcher-darwin-x64": "2.5.6",
"@parcel/watcher-freebsd-x64": "2.5.6",
"@parcel/watcher-linux-arm-glibc": "2.5.6",
"@parcel/watcher-linux-arm-musl": "2.5.6",
"@parcel/watcher-linux-arm64-glibc": "2.5.6",
"@parcel/watcher-linux-arm64-musl": "2.5.6",
"@parcel/watcher-linux-x64-glibc": "2.5.6",
"@parcel/watcher-linux-x64-musl": "2.5.6",
"@parcel/watcher-win32-arm64": "2.5.6",
"@parcel/watcher-win32-ia32": "2.5.6",
"@parcel/watcher-win32-x64": "2.5.6"
}
},
"node_modules/@parcel/watcher-win32-x64": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
"integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
"node_modules/@parcel/watcher-linux-x64-glibc": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz",
"integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
"linux"
],
"engines": {
"node": ">= 10.0.0"
@ -119,149 +104,99 @@
}
},
"node_modules/@tailwindcss/cli": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.1.11.tgz",
"integrity": "sha512-7RAFOrVaXCFz5ooEG36Kbh+sMJiI2j4+Ozp71smgjnLfBRu7DTfoq8DsTvzse2/6nDeo2M3vS/FGaxfDgr3rtQ==",
"version": "4.1.18",
"resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.1.18.tgz",
"integrity": "sha512-sMZ+lZbDyxwjD2E0L7oRUjJ01Ffjtme5OtjvvnC+cV4CEDcbqzbp25TCpxHj6kWLU9+DlqJOiNgSOgctC2aZmg==",
"license": "MIT",
"dependencies": {
"@parcel/watcher": "^2.5.1",
"@tailwindcss/node": "4.1.11",
"@tailwindcss/oxide": "4.1.11",
"enhanced-resolve": "^5.18.1",
"@tailwindcss/node": "4.1.18",
"@tailwindcss/oxide": "4.1.18",
"enhanced-resolve": "^5.18.3",
"mri": "^1.2.0",
"picocolors": "^1.1.1",
"tailwindcss": "4.1.11"
"tailwindcss": "4.1.18"
},
"bin": {
"tailwindcss": "dist/index.mjs"
}
},
"node_modules/@tailwindcss/node": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz",
"integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==",
"version": "4.1.18",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz",
"integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==",
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.3.0",
"enhanced-resolve": "^5.18.1",
"jiti": "^2.4.2",
"lightningcss": "1.30.1",
"magic-string": "^0.30.17",
"@jridgewell/remapping": "^2.3.4",
"enhanced-resolve": "^5.18.3",
"jiti": "^2.6.1",
"lightningcss": "1.30.2",
"magic-string": "^0.30.21",
"source-map-js": "^1.2.1",
"tailwindcss": "4.1.11"
"tailwindcss": "4.1.18"
}
},
"node_modules/@tailwindcss/oxide": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz",
"integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==",
"hasInstallScript": true,
"version": "4.1.18",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz",
"integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==",
"license": "MIT",
"dependencies": {
"detect-libc": "^2.0.4",
"tar": "^7.4.3"
},
"engines": {
"node": ">= 10"
},
"optionalDependencies": {
"@tailwindcss/oxide-android-arm64": "4.1.11",
"@tailwindcss/oxide-darwin-arm64": "4.1.11",
"@tailwindcss/oxide-darwin-x64": "4.1.11",
"@tailwindcss/oxide-freebsd-x64": "4.1.11",
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11",
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.11",
"@tailwindcss/oxide-linux-arm64-musl": "4.1.11",
"@tailwindcss/oxide-linux-x64-gnu": "4.1.11",
"@tailwindcss/oxide-linux-x64-musl": "4.1.11",
"@tailwindcss/oxide-wasm32-wasi": "4.1.11",
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.11",
"@tailwindcss/oxide-win32-x64-msvc": "4.1.11"
"@tailwindcss/oxide-android-arm64": "4.1.18",
"@tailwindcss/oxide-darwin-arm64": "4.1.18",
"@tailwindcss/oxide-darwin-x64": "4.1.18",
"@tailwindcss/oxide-freebsd-x64": "4.1.18",
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18",
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.18",
"@tailwindcss/oxide-linux-arm64-musl": "4.1.18",
"@tailwindcss/oxide-linux-x64-gnu": "4.1.18",
"@tailwindcss/oxide-linux-x64-musl": "4.1.18",
"@tailwindcss/oxide-wasm32-wasi": "4.1.18",
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.18",
"@tailwindcss/oxide-win32-x64-msvc": "4.1.18"
}
},
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz",
"integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==",
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
"version": "4.1.18",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz",
"integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
"linux"
],
"engines": {
"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": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"license": "Apache-2.0",
"bin": {
"detect-libc": "bin/detect-libc.js"
},
"engines": {
"node": ">=0.10"
"node": ">=8"
}
},
"node_modules/enhanced-resolve": {
"version": "5.18.2",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz",
"integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==",
"version": "5.19.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz",
"integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
"tapable": "^2.2.0"
"tapable": "^2.3.0"
},
"engines": {
"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": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@ -289,28 +224,19 @@
"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": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
"integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==",
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"license": "MIT",
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
},
"node_modules/lightningcss": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
"integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
"version": "1.30.2",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
"integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
"license": "MPL-2.0",
"dependencies": {
"detect-libc": "^2.0.3"
@ -323,29 +249,30 @@
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"lightningcss-darwin-arm64": "1.30.1",
"lightningcss-darwin-x64": "1.30.1",
"lightningcss-freebsd-x64": "1.30.1",
"lightningcss-linux-arm-gnueabihf": "1.30.1",
"lightningcss-linux-arm64-gnu": "1.30.1",
"lightningcss-linux-arm64-musl": "1.30.1",
"lightningcss-linux-x64-gnu": "1.30.1",
"lightningcss-linux-x64-musl": "1.30.1",
"lightningcss-win32-arm64-msvc": "1.30.1",
"lightningcss-win32-x64-msvc": "1.30.1"
"lightningcss-android-arm64": "1.30.2",
"lightningcss-darwin-arm64": "1.30.2",
"lightningcss-darwin-x64": "1.30.2",
"lightningcss-freebsd-x64": "1.30.2",
"lightningcss-linux-arm-gnueabihf": "1.30.2",
"lightningcss-linux-arm64-gnu": "1.30.2",
"lightningcss-linux-arm64-musl": "1.30.2",
"lightningcss-linux-x64-gnu": "1.30.2",
"lightningcss-linux-x64-musl": "1.30.2",
"lightningcss-win32-arm64-msvc": "1.30.2",
"lightningcss-win32-x64-msvc": "1.30.2"
}
},
"node_modules/lightningcss-win32-x64-msvc": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz",
"integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==",
"node_modules/lightningcss-linux-x64-gnu": {
"version": "1.30.2",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
"integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
"linux"
],
"engines": {
"node": ">= 12.0.0"
@ -355,71 +282,13 @@
"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": {
"version": "0.30.17",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0"
}
},
"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"
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/mri": {
@ -444,12 +313,12 @@
"license": "ISC"
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
"engines": {
"node": ">=8.6"
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
@ -465,56 +334,22 @@
}
},
"node_modules/tailwindcss": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz",
"integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==",
"version": "4.1.18",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
"integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
"license": "MIT"
},
"node_modules/tapable": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz",
"integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==",
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
"integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/tar": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
"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"
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
}
}

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
};

View File

@ -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"
}
}

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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"}

View File

@ -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

View File

@ -1,3 +0,0 @@
{
"type": "commonjs"
}

View File

@ -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

View File

@ -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"}

View File

@ -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

View File

@ -1,3 +0,0 @@
{
"type": "module"
}

View File

@ -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"
}
}

View File

@ -224,4 +224,4 @@ Fastest is gen-mapping: decoded output
```
[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

View File

@ -1,7 +1,19 @@
(function (global, factory, m) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(module, require('@jridgewell/sourcemap-codec'), require('@jridgewell/trace-mapping')) :
typeof define === 'function' && define.amd ? define(['module', '@jridgewell/sourcemap-codec', '@jridgewell/trace-mapping'], factory) :
(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);
(function (global, factory) {
if (typeof exports === 'object' && typeof module !== 'undefined') {
factory(module, require('@jridgewell/sourcemap-codec'), require('@jridgewell/trace-mapping'));
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) {
"use strict";
var __create = Object.create;

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{
"name": "@jridgewell/gen-mapping",
"version": "0.3.12",
"version": "0.3.13",
"description": "Generate source maps",
"keywords": [
"source",
@ -21,11 +21,7 @@
"types": "./types/gen-mapping.d.mts",
"default": "./dist/gen-mapping.mjs"
},
"require": {
"types": "./types/gen-mapping.d.cts",
"default": "./dist/gen-mapping.umd.js"
},
"browser": {
"default": {
"types": "./types/gen-mapping.d.cts",
"default": "./dist/gen-mapping.umd.js"
}

View File

@ -1,7 +1,19 @@
(function (global, factory, m) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(module) :
typeof define === 'function' && define.amd ? define(['module'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(m = { exports: {} }), global.sourcemapCodec = 'default' in m.exports ? m.exports.default : m.exports);
(function (global, factory) {
if (typeof exports === 'object' && typeof module !== 'undefined') {
factory(module);
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) {
"use strict";
var __defProp = Object.defineProperty;

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{
"name": "@jridgewell/sourcemap-codec",
"version": "1.5.4",
"version": "1.5.5",
"description": "Encode/decode sourcemap mappings",
"keywords": [
"sourcemap",
@ -21,11 +21,7 @@
"types": "./types/sourcemap-codec.d.mts",
"default": "./dist/sourcemap-codec.mjs"
},
"require": {
"types": "./types/sourcemap-codec.d.cts",
"default": "./dist/sourcemap-codec.umd.js"
},
"browser": {
"default": {
"types": "./types/sourcemap-codec.d.cts",
"default": "./dist/sourcemap-codec.umd.js"
}

View File

@ -59,6 +59,32 @@ function sortComparator(a, b) {
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
var found = false;
function binarySearch(haystack, needle, low, high) {
@ -117,41 +143,6 @@ function memoizedBinarySearch(haystack, needle, state, key) {
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
function parse(map) {
return typeof map === "string" ? JSON.parse(map) : map;
@ -461,7 +452,7 @@ function sliceGeneratedPositions(segments, memo, line, column, bias) {
return result;
}
function generatedPosition(map, source, line, column, bias, all) {
var _a;
var _a, _b;
line--;
if (line < 0) throw new Error(LINE_GTR_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);
if (sourceIndex2 === -1) sourceIndex2 = resolvedSources.indexOf(source);
if (sourceIndex2 === -1) return all ? [] : GMapping(null, null);
const generated = (_a = cast(map))._bySources || (_a._bySources = buildBySources(
decodedMappings(map),
cast(map)._bySourceMemos = sources.map(memoizedState)
));
const bySourceMemos = (_a = cast(map))._bySourceMemos || (_a._bySourceMemos = sources.map(memoizedState));
const generated = (_b = cast(map))._bySources || (_b._bySources = buildBySources(decodedMappings(map), bySourceMemos));
const segments = generated[sourceIndex2][line];
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);
const index = traceSegmentInternal(segments, memo, line, column, bias);
if (index === -1) return GMapping(null, null);

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,19 @@
(function (global, factory, m) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(module, require('@jridgewell/resolve-uri'), require('@jridgewell/sourcemap-codec')) :
typeof define === 'function' && define.amd ? define(['module', '@jridgewell/resolve-uri', '@jridgewell/sourcemap-codec'], factory) :
(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);
(function (global, factory) {
if (typeof exports === 'object' && typeof module !== 'undefined') {
factory(module, require('@jridgewell/resolve-uri'), require('@jridgewell/sourcemap-codec'));
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) {
"use strict";
var __create = Object.create;
@ -131,6 +143,32 @@ function sortComparator(a, b) {
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
var found = false;
function binarySearch(haystack, needle, low, high) {
@ -189,41 +227,6 @@ function memoizedBinarySearch(haystack, needle, state, key) {
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
function parse(map) {
return typeof map === "string" ? JSON.parse(map) : map;
@ -533,7 +536,7 @@ function sliceGeneratedPositions(segments, memo, line, column, bias) {
return result;
}
function generatedPosition(map, source, line, column, bias, all) {
var _a;
var _a, _b;
line--;
if (line < 0) throw new Error(LINE_GTR_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);
if (sourceIndex2 === -1) sourceIndex2 = resolvedSources.indexOf(source);
if (sourceIndex2 === -1) return all ? [] : GMapping(null, null);
const generated = (_a = cast(map))._bySources || (_a._bySources = buildBySources(
decodedMappings(map),
cast(map)._bySourceMemos = sources.map(memoizedState)
));
const bySourceMemos = (_a = cast(map))._bySourceMemos || (_a._bySourceMemos = sources.map(memoizedState));
const generated = (_b = cast(map))._bySources || (_b._bySources = buildBySources(decodedMappings(map), bySourceMemos));
const segments = generated[sourceIndex2][line];
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);
const index = traceSegmentInternal(segments, memo, line, column, bias);
if (index === -1) return GMapping(null, null);

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{
"name": "@jridgewell/trace-mapping",
"version": "0.3.29",
"version": "0.3.31",
"description": "Trace the original position through a source map",
"keywords": [
"source",
@ -21,11 +21,7 @@
"types": "./types/trace-mapping.d.mts",
"default": "./dist/trace-mapping.mjs"
},
"require": {
"types": "./types/trace-mapping.d.cts",
"default": "./dist/trace-mapping.umd.js"
},
"browser": {
"default": {
"types": "./types/trace-mapping.d.cts",
"default": "./dist/trace-mapping.umd.js"
}
@ -37,7 +33,7 @@
"scripts": {
"benchmark": "run-s build:code benchmark:*",
"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:code": "node ../../esbuild.mjs trace-mapping.ts",
"build:types": "run-s build:types:force build:types:emit build:types:mts",

View File

@ -1,21 +1,17 @@
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 { MemoState } from './binary-search';
export type Source = {
__proto__: null;
[line: number]: Exclude<ReverseSegment, [number]>[];
};
export type Source = ReverseSegment[][];
// Rebuilds the original source files, with mappings that are ordered by source line/column instead
// of generated line/column.
export default function buildBySources(
decoded: readonly SourceMapSegment[][],
memos: MemoState[],
memos: unknown[],
): Source[] {
const sources: Source[] = memos.map(buildNullArray);
const sources: Source[] = memos.map(() => []);
for (let i = 0; i < decoded.length; i++) {
const line = decoded[i];
@ -26,40 +22,20 @@ export default function buildBySources(
const sourceIndex = seg[SOURCES_INDEX];
const sourceLine = seg[SOURCE_LINE];
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
// segment should go. Either way, we want to insert after that. And there may be multiple
// generated segments associated with an original location, so there may need to move several
// indexes before we find where we need to insert.
let index = upperBound(
originalLine,
sourceColumn,
memoizedBinarySearch(originalLine, sourceColumn, memo, sourceLine),
);
const source = sources[sourceIndex];
const segs = (source[sourceLine] ||= []);
segs.push([sourceColumn, i, seg[COLUMN]]);
}
}
memo.lastIndex = ++index;
insert(originalLine, index, [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;
}
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;
}

View File

@ -1,6 +1,6 @@
import { COLUMN } from './sourcemap-segment';
import type { SourceMapSegment } from './sourcemap-segment';
import type { ReverseSegment, SourceMapSegment } from './sourcemap-segment';
export default function maybeSort(
mappings: SourceMapSegment[][],
@ -40,6 +40,6 @@ function sortSegments(line: SourceMapSegment[], owned: boolean): SourceMapSegmen
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];
}

View File

@ -484,15 +484,13 @@ function generatedPosition(
if (sourceIndex === -1) sourceIndex = resolvedSources.indexOf(source);
if (sourceIndex === -1) return all ? [] : GMapping(null, null);
const generated = (cast(map)._bySources ||= buildBySources(
decodedMappings(map),
(cast(map)._bySourceMemos = sources.map(memoizedState)),
));
const bySourceMemos = (cast(map)._bySourceMemos ||= sources.map(memoizedState));
const generated = (cast(map)._bySources ||= buildBySources(decodedMappings(map), bySourceMemos));
const segments = generated[sourceIndex][line];
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);

View File

@ -1,8 +1,4 @@
import type { ReverseSegment, SourceMapSegment } from './sourcemap-segment.cts';
import type { MemoState } from './binary-search.cts';
export type Source = {
__proto__: null;
[line: number]: Exclude<ReverseSegment, [number]>[];
};
export = function buildBySources(decoded: readonly SourceMapSegment[][], memos: MemoState[]): Source[];
export type Source = ReverseSegment[][];
export = function buildBySources(decoded: readonly SourceMapSegment[][], memos: unknown[]): Source[];
//# sourceMappingURL=by-source.d.ts.map

View File

@ -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"}

View File

@ -1,8 +1,4 @@
import type { ReverseSegment, SourceMapSegment } from './sourcemap-segment.mts';
import type { MemoState } from './binary-search.mts';
export type Source = {
__proto__: null;
[line: number]: Exclude<ReverseSegment, [number]>[];
};
export default function buildBySources(decoded: readonly SourceMapSegment[][], memos: MemoState[]): Source[];
export type Source = ReverseSegment[][];
export default function buildBySources(decoded: readonly SourceMapSegment[][], memos: unknown[]): Source[];
//# sourceMappingURL=by-source.d.ts.map

View File

@ -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"}

View File

@ -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 declare function sortComparator<T extends SourceMapSegment | ReverseSegment>(a: T, b: T): number;
//# sourceMappingURL=sort.d.ts.map

View File

@ -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"}

View File

@ -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 declare function sortComparator<T extends SourceMapSegment | ReverseSegment>(a: T, b: T): number;
//# sourceMappingURL=sort.d.ts.map

View File

@ -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"}

View File

@ -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.

View File

@ -1 +0,0 @@
This is the win32-x64 build of @parcel/watcher. See https://github.com/parcel-bundler/watcher for details.

View File

@ -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.

View File

@ -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.
- `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.
- 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.
@ -129,6 +129,7 @@ subscribe(/* ... */);
- [Gatsby Cloud](https://twitter.com/chatsidhartha/status/1435647412828196867)
- [Nx](https://nx.dev)
- [Nuxt](https://nuxt.com)
- [Meteor](https://github.com/meteor/meteor)
## License

View File

@ -7,6 +7,7 @@
"include_dirs" : ["<!(node -p \"require('node-addon-api').include_dir\")"],
'cflags!': [ '-fno-exceptions', '-std=c++17' ],
'cflags_cc!': [ '-fno-exceptions', '-std=c++17' ],
'cflags': [ '-fstack-protector-strong' ],
"conditions": [
['OS=="mac"', {
"sources": [
@ -65,7 +66,22 @@
"msvs_settings": {
"VCCLCompilerTool": {
"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"
]
}
}
}],

View File

@ -2,7 +2,8 @@ const {createWrapper} = require('./wrapper');
let name = `@parcel/watcher-${process.platform}-${process.arch}`;
if (process.platform === 'linux') {
const { MUSL, family } = require('detect-libc');
const { MUSL, familySync } = require('detect-libc');
const family = familySync();
if (family === MUSL) {
name += '-musl';
} else {

View File

@ -1,6 +1,6 @@
{
"name": "@parcel/watcher",
"version": "2.5.1",
"version": "2.5.6",
"main": "index.js",
"types": "index.d.ts",
"repository": {
@ -50,10 +50,10 @@
]
},
"dependencies": {
"detect-libc": "^1.0.3",
"detect-libc": "^2.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": {
"esbuild": "^0.19.8",
@ -71,18 +71,18 @@
]
},
"optionalDependencies": {
"@parcel/watcher-darwin-x64": "2.5.1",
"@parcel/watcher-darwin-arm64": "2.5.1",
"@parcel/watcher-win32-x64": "2.5.1",
"@parcel/watcher-win32-arm64": "2.5.1",
"@parcel/watcher-win32-ia32": "2.5.1",
"@parcel/watcher-linux-x64-glibc": "2.5.1",
"@parcel/watcher-linux-x64-musl": "2.5.1",
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
"@parcel/watcher-linux-arm64-musl": "2.5.1",
"@parcel/watcher-linux-arm-glibc": "2.5.1",
"@parcel/watcher-linux-arm-musl": "2.5.1",
"@parcel/watcher-android-arm64": "2.5.1",
"@parcel/watcher-freebsd-x64": "2.5.1"
"@parcel/watcher-darwin-x64": "2.5.6",
"@parcel/watcher-darwin-arm64": "2.5.6",
"@parcel/watcher-win32-x64": "2.5.6",
"@parcel/watcher-win32-arm64": "2.5.6",
"@parcel/watcher-win32-ia32": "2.5.6",
"@parcel/watcher-linux-x64-glibc": "2.5.6",
"@parcel/watcher-linux-x64-musl": "2.5.6",
"@parcel/watcher-linux-arm64-glibc": "2.5.6",
"@parcel/watcher-linux-arm64-musl": "2.5.6",
"@parcel/watcher-linux-arm-glibc": "2.5.6",
"@parcel/watcher-linux-arm-musl": "2.5.6",
"@parcel/watcher-android-arm64": "2.5.6",
"@parcel/watcher-freebsd-x64": "2.5.6"
}
}

View File

@ -21,7 +21,11 @@
#include "Backend.hh"
#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) {
// 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) {
auto found = sharedBackends.find(backend);
if (found != sharedBackends.end()) {
auto found = getSharedBackends().find(backend);
if (found != getSharedBackends().end()) {
return found->second;
}
@ -76,21 +80,21 @@ std::shared_ptr<Backend> Backend::getShared(std::string backend) {
}
result->run();
sharedBackends.emplace(backend, result);
getSharedBackends().emplace(backend, result);
return result;
}
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) {
sharedBackends.erase(it);
getSharedBackends().erase(it);
break;
}
}
// Free up memory.
if (sharedBackends.size() == 0) {
sharedBackends.rehash(0);
if (getSharedBackends().size() == 0) {
getSharedBackends().rehash(0);
}
}
@ -145,7 +149,7 @@ void Backend::watch(WatcherRef watcher) {
try {
this->subscribe(watcher);
mSubscriptions.insert(watcher);
} catch (std::exception &err) {
} catch (std::exception&) {
unref();
throw;
}

Some files were not shown because too many files have changed in this diff Show More