60663a461c
A user could select another local file with the same name as one that already exists in completed storage. The upload would be allowed to start and only hit an existing-file conflict late in the flow, which made the UI look like the file was uploadable. Reject duplicate sanitized names during upload creation so no staging record or chunk transfer starts for a file that cannot be completed. Keep the completion path non-replacing as a second guard by promoting through a no-overwrite file creation path, with a hard-link fast path and copy fallback for custom temp locations. The browser now treats the server's duplicate-name conflict as a terminal row: it disables the action, marks the item visually, and tells the user to rename the file if they want to upload that copy. Test Plan: - just check Refs: none
291 lines
4.1 KiB
CSS
291 lines
4.1 KiB
CSS
:root {
|
|
color-scheme: light dark;
|
|
--bg: #f6f7f9;
|
|
--panel: #ffffff;
|
|
--text: #1d2430;
|
|
--muted: #667085;
|
|
--line: #d0d5dd;
|
|
--accent: #147a73;
|
|
--accent-strong: #0f5f59;
|
|
--track: #e4e7ec;
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
margin: 0;
|
|
min-height: 100vh;
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
font-family:
|
|
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
|
sans-serif;
|
|
}
|
|
|
|
button,
|
|
input {
|
|
font: inherit;
|
|
}
|
|
|
|
[hidden] {
|
|
display: none !important;
|
|
}
|
|
|
|
.app-shell {
|
|
width: min(720px, 100%);
|
|
margin: 0 auto;
|
|
padding: 32px 16px;
|
|
}
|
|
|
|
.upload-panel {
|
|
display: grid;
|
|
gap: 20px;
|
|
padding: 24px;
|
|
border: 1px solid var(--line);
|
|
border-radius: 8px;
|
|
background: var(--panel);
|
|
box-shadow: 0 10px 30px rgb(16 24 40 / 8%);
|
|
}
|
|
|
|
.panel-heading {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
gap: 16px;
|
|
}
|
|
|
|
h1,
|
|
p {
|
|
margin: 0;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 2rem;
|
|
line-height: 1.1;
|
|
}
|
|
|
|
.subtle {
|
|
margin-top: 6px;
|
|
color: var(--muted);
|
|
}
|
|
|
|
.file-picker {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 18px;
|
|
border: 1px dashed var(--line);
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.file-picker input {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
overflow: hidden;
|
|
clip: rect(0, 0, 0, 0);
|
|
}
|
|
|
|
.upload-section {
|
|
display: grid;
|
|
gap: 12px;
|
|
}
|
|
|
|
.upload-list {
|
|
display: grid;
|
|
gap: 12px;
|
|
margin: 0;
|
|
padding: 0;
|
|
list-style: none;
|
|
}
|
|
|
|
.upload-item {
|
|
display: grid;
|
|
gap: 12px;
|
|
padding: 12px;
|
|
border: 1px solid var(--line);
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.upload-item-blocked {
|
|
border-color: #f04438;
|
|
background: rgb(240 68 56 / 8%);
|
|
}
|
|
|
|
.upload-item-blocked .upload-meta span {
|
|
color: #b42318;
|
|
}
|
|
|
|
.upload-item-header {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr) auto;
|
|
align-items: start;
|
|
gap: 12px;
|
|
}
|
|
|
|
.upload-meta {
|
|
display: grid;
|
|
gap: 4px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.upload-meta span {
|
|
color: var(--muted);
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.upload-progress {
|
|
display: grid;
|
|
gap: 6px;
|
|
}
|
|
|
|
.progress-wrap {
|
|
height: 14px;
|
|
overflow: hidden;
|
|
border-radius: 999px;
|
|
background: var(--track);
|
|
}
|
|
|
|
.progress-bar {
|
|
width: 0%;
|
|
height: 100%;
|
|
background: var(--accent);
|
|
transition: width 180ms ease;
|
|
}
|
|
|
|
.progress-meta {
|
|
color: var(--muted);
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.actions {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
}
|
|
|
|
.upload-item-actions {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
justify-content: flex-end;
|
|
gap: 8px;
|
|
}
|
|
|
|
button {
|
|
min-width: 96px;
|
|
min-height: 40px;
|
|
border: 1px solid transparent;
|
|
border-radius: 6px;
|
|
color: #ffffff;
|
|
background: var(--accent);
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
}
|
|
|
|
button.secondary {
|
|
border-color: var(--line);
|
|
color: var(--text);
|
|
background: transparent;
|
|
}
|
|
|
|
button.danger {
|
|
border-color: #b42318;
|
|
color: #b42318;
|
|
background: transparent;
|
|
}
|
|
|
|
button:disabled {
|
|
color: var(--muted);
|
|
background: var(--track);
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.pending-section {
|
|
display: grid;
|
|
gap: 12px;
|
|
}
|
|
|
|
h2 {
|
|
margin: 0;
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.pending-list {
|
|
display: grid;
|
|
gap: 10px;
|
|
margin: 0;
|
|
padding: 0;
|
|
list-style: none;
|
|
}
|
|
|
|
.pending-item {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr) auto auto;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 12px;
|
|
border: 1px solid var(--line);
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.pending-item strong,
|
|
.upload-meta strong {
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.pending-meta {
|
|
display: grid;
|
|
gap: 4px;
|
|
}
|
|
|
|
.pending-meta span {
|
|
color: var(--muted);
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.event-log {
|
|
min-height: 80px;
|
|
margin: 0;
|
|
padding: 14px 14px 14px 32px;
|
|
border: 1px solid var(--line);
|
|
border-radius: 8px;
|
|
color: var(--muted);
|
|
}
|
|
|
|
@media (prefers-color-scheme: dark) {
|
|
:root {
|
|
--bg: #14161a;
|
|
--panel: #1f242b;
|
|
--text: #f2f4f7;
|
|
--muted: #b8c0cc;
|
|
--line: #3d4654;
|
|
--accent: #35b8aa;
|
|
--accent-strong: #9ce4dc;
|
|
--track: #343b46;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 520px) {
|
|
.panel-heading {
|
|
display: grid;
|
|
}
|
|
|
|
.upload-panel {
|
|
padding: 18px;
|
|
}
|
|
|
|
.pending-item {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.upload-item-header {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.upload-item-actions {
|
|
justify-content: flex-start;
|
|
}
|
|
}
|