💄 完善我的图片页面

This commit is contained in:
WispX 2021-12-19 22:31:14 +08:00
parent 01b4502c45
commit 4518bfdbce
27 changed files with 304 additions and 5571 deletions

View File

@ -3,17 +3,32 @@
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use App\Models\Image;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
class ImageController extends Controller
{
public function index(): View
public function index(Request $request): View|Response
{
/** @var User $user */
$user = Auth::user();
if ($request->method() === 'POST') {
$images = $user->images()->latest()->paginate(40);
$images->getCollection()->each(function (Image $image) {
$image->human_date = $image->created_at->diffForHumans();
$image->date = $image->created_at->format('Y-m-d H:i:s');
$image->append(['url', 'filename'])->setVisible([
'id', 'filename', 'url', 'human_date', 'date', 'human_date'
]);
});
return $this->success('success', compact('images'));
}
return view('images');
}
}

View File

@ -9,7 +9,6 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
/**
* @property int $id
@ -21,6 +20,7 @@ use Illuminate\Support\Str;
* @property string $pathname
* @property string $origin_name
* @property string $alias_name
* @property string $filename
* @property float $size
* @property string $mimetype
* @property string $md5
@ -68,6 +68,11 @@ class Image extends Model
'is_unhealthy' => 'bool',
];
public function getFilenameAttribute(): string
{
return $this->alias_name ?: $this->origin_name;
}
public function getPathnameAttribute(): string
{
return "{$this->path}/{$this->name}";

17
package-lock.json generated
View File

@ -22,12 +22,12 @@
"less": "^4.1.2",
"less-loader": "^10.2.0",
"lodash": "^4.17.19",
"photoswipe": "^4.1.3",
"postcss": "^8.2.1",
"postcss-import": "^12.0.1",
"resolve-url-loader": "^4.0.0",
"tailwindcss": "^3.0.0",
"toastr": "^2.1.4"
"toastr": "^2.1.4",
"viewerjs": "^1.10.2"
}
},
"node_modules/@babel/code-frame": {
@ -10111,6 +10111,13 @@
"node": ">= 0.8"
}
},
"node_modules/viewerjs": {
"version": "1.10.2",
"resolved": "https://repo.huaweicloud.com/repository/npm/viewerjs/-/viewerjs-1.10.2.tgz",
"integrity": "sha512-v/4+OUF/71JthlvYuqfse+WGkMJO0LxvWiOLsAoxWw+RWjMdEBWn0ZQ5Mc+OhNIFd9uLhG62GzfFIplvix8odg==",
"dev": true,
"license": "MIT"
},
"node_modules/vm-browserify": {
"version": "1.1.2",
"resolved": "https://repo.huaweicloud.com/repository/npm/vm-browserify/-/vm-browserify-1.1.2.tgz",
@ -17894,6 +17901,12 @@
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
"dev": true
},
"viewerjs": {
"version": "1.10.2",
"resolved": "https://repo.huaweicloud.com/repository/npm/viewerjs/-/viewerjs-1.10.2.tgz",
"integrity": "sha512-v/4+OUF/71JthlvYuqfse+WGkMJO0LxvWiOLsAoxWw+RWjMdEBWn0ZQ5Mc+OhNIFd9uLhG62GzfFIplvix8odg==",
"dev": true
},
"vm-browserify": {
"version": "1.1.2",
"resolved": "https://repo.huaweicloud.com/repository/npm/vm-browserify/-/vm-browserify-1.1.2.tgz",

View File

@ -27,11 +27,11 @@
"less": "^4.1.2",
"less-loader": "^10.2.0",
"lodash": "^4.17.19",
"photoswipe": "^4.1.3",
"postcss": "^8.2.1",
"postcss-import": "^12.0.1",
"resolve-url-loader": "^4.0.0",
"tailwindcss": "^3.0.0",
"toastr": "^2.1.4"
"toastr": "^2.1.4",
"viewerjs": "^1.10.2"
}
}

100
public/css/app.css vendored
View File

@ -636,6 +636,9 @@ select {
white-space: nowrap;
border-width: 0;
}
.static {
position: static;
}
.fixed {
position: fixed;
}
@ -645,6 +648,10 @@ select {
.relative {
position: relative;
}
.sticky {
position: -webkit-sticky;
position: sticky;
}
.inset-0 {
top: 0px;
right: 0px;
@ -678,6 +685,15 @@ select {
.bottom-0 {
bottom: 0px;
}
.left-2 {
left: 0.5rem;
}
.bottom-2 {
bottom: 0.5rem;
}
.top-14 {
top: 3.5rem;
}
.z-0 {
z-index: 0;
}
@ -690,6 +706,9 @@ select {
.z-\[1\] {
z-index: 1;
}
.z-\[2\] {
z-index: 2;
}
.m-2 {
margin: 0.5rem;
}
@ -764,9 +783,18 @@ select {
.mr-4 {
margin-right: 1rem;
}
.ml-64 {
margin-left: 16rem;
}
.mb-6 {
margin-bottom: 1.5rem;
}
.block {
display: block;
}
.inline {
display: inline;
}
.flex {
display: flex;
}
@ -860,6 +888,18 @@ select {
.w-36 {
width: 9rem;
}
.w-\[90\%\] {
width: 90%;
}
.w-64 {
width: 16rem;
}
.w-72 {
width: 18rem;
}
.w-80 {
width: 20rem;
}
.max-w-xl {
max-width: 36rem;
}
@ -925,6 +965,9 @@ select {
.cursor-pointer {
cursor: pointer;
}
.cursor-not-allowed {
cursor: not-allowed;
}
.select-all {
-webkit-user-select: all;
-moz-user-select: all;
@ -1017,6 +1060,9 @@ select {
.overflow-scroll {
overflow: scroll;
}
.overflow-y-scroll {
overflow-y: scroll;
}
.overscroll-contain {
-ms-scroll-chaining: none;
overscroll-behavior: contain;
@ -1113,10 +1159,6 @@ select {
--tw-border-opacity: 1;
border-color: rgb(243 244 246 / var(--tw-border-opacity));
}
.border-blue-500 {
--tw-border-opacity: 1;
border-color: rgb(59 130 246 / var(--tw-border-opacity));
}
.bg-white {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
@ -1164,9 +1206,25 @@ select {
.bg-opacity-75 {
--tw-bg-opacity: 0.75;
}
.bg-gradient-to-t {
background-image: linear-gradient(to top, var(--tw-gradient-stops));
}
.from-black {
--tw-gradient-from: #000;
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgb(0 0 0 / 0));
}
.fill-current {
fill: currentColor;
}
.fill-blue-500 {
fill: #3b82f6;
}
.fill-gray-300 {
fill: #d1d5db;
}
.fill-red-300 {
fill: #fca5a5;
}
.p-4 {
padding: 1rem;
}
@ -1259,6 +1317,24 @@ select {
.pl-0 {
padding-left: 0px;
}
.pt-4 {
padding-top: 1rem;
}
.pb-4 {
padding-bottom: 1rem;
}
.pt-16 {
padding-top: 4rem;
}
.pt-24 {
padding-top: 6rem;
}
.pt-20 {
padding-top: 5rem;
}
.pt-2 {
padding-top: 0.5rem;
}
.text-center {
text-align: center;
}
@ -1654,10 +1730,6 @@ select {
.group:hover .group-hover\:block {
display: block;
}
.group:hover .group-hover\:border-blue-500 {
--tw-border-opacity: 1;
border-color: rgb(59 130 246 / var(--tw-border-opacity));
}
.dark .dark\:bg-gray-900 {
--tw-bg-opacity: 1;
background-color: rgb(17 24 39 / var(--tw-bg-opacity));
@ -1759,6 +1831,10 @@ select {
margin-top: 0px;
}
.md\:mt-2 {
margin-top: 0.5rem;
}
.md\:block {
display: block;
}
@ -1813,6 +1889,14 @@ select {
padding-right: 2.5rem;
}
.md\:pt-24 {
padding-top: 6rem;
}
.md\:pt-4 {
padding-top: 1rem;
}
.md\:hover\:w-52:hover {
width: 13rem;
}

View File

@ -239,4 +239,7 @@ progress::-webkit-progress-value {
.toast-close-button {
right: -0.15em;
}
.viewer-backdrop {
background-color: rgba(0, 0, 0, 0.75);
}

View File

@ -1,482 +0,0 @@
/*! PhotoSwipe Default UI CSS by Dmitry Semenov | photoswipe.com | MIT license */
/*
Contents:
1. Buttons
2. Share modal and links
3. Index indicator ("1 of X" counter)
4. Caption
5. Loading indicator
6. Additional styles (root element, top bar, idle state, hidden state, etc.)
*/
/*
1. Buttons
*/
/* <button> css reset */
.pswp__button {
width: 44px;
height: 44px;
position: relative;
background: none;
cursor: pointer;
overflow: visible;
-webkit-appearance: none;
display: block;
border: 0;
padding: 0;
margin: 0;
float: right;
opacity: 0.75;
-webkit-transition: opacity 0.2s;
transition: opacity 0.2s;
-webkit-box-shadow: none;
box-shadow: none; }
.pswp__button:focus, .pswp__button:hover {
opacity: 1; }
.pswp__button:active {
outline: none;
opacity: 0.9; }
.pswp__button::-moz-focus-inner {
padding: 0;
border: 0; }
/* pswp__ui--over-close class it added when mouse is over element that should close gallery */
.pswp__ui--over-close .pswp__button--close {
opacity: 1; }
.pswp__button,
.pswp__button--arrow--left:before,
.pswp__button--arrow--right:before {
background: url(default-skin.png) 0 0 no-repeat;
background-size: 264px 88px;
width: 44px;
height: 44px; }
@media (-webkit-min-device-pixel-ratio: 1.1), (-webkit-min-device-pixel-ratio: 1.09375), (min-resolution: 105dpi), (min-resolution: 1.1dppx) {
/* Serve SVG sprite if browser supports SVG and resolution is more than 105dpi */
.pswp--svg .pswp__button,
.pswp--svg .pswp__button--arrow--left:before,
.pswp--svg .pswp__button--arrow--right:before {
background-image: url(default-skin.svg); }
.pswp--svg .pswp__button--arrow--left,
.pswp--svg .pswp__button--arrow--right {
background: none; } }
.pswp__button--close {
background-position: 0 -44px; }
.pswp__button--share {
background-position: -44px -44px; }
.pswp__button--fs {
display: none; }
.pswp--supports-fs .pswp__button--fs {
display: block; }
.pswp--fs .pswp__button--fs {
background-position: -44px 0; }
.pswp__button--zoom {
display: none;
background-position: -88px 0; }
.pswp--zoom-allowed .pswp__button--zoom {
display: block; }
.pswp--zoomed-in .pswp__button--zoom {
background-position: -132px 0; }
/* no arrows on touch screens */
.pswp--touch .pswp__button--arrow--left,
.pswp--touch .pswp__button--arrow--right {
visibility: hidden; }
/*
Arrow buttons hit area
(icon is added to :before pseudo-element)
*/
.pswp__button--arrow--left,
.pswp__button--arrow--right {
background: none;
top: 50%;
margin-top: -50px;
width: 70px;
height: 100px;
position: absolute; }
.pswp__button--arrow--left {
left: 0; }
.pswp__button--arrow--right {
right: 0; }
.pswp__button--arrow--left:before,
.pswp__button--arrow--right:before {
content: '';
top: 35px;
background-color: rgba(0, 0, 0, 0.3);
height: 30px;
width: 32px;
position: absolute; }
.pswp__button--arrow--left:before {
left: 6px;
background-position: -138px -44px; }
.pswp__button--arrow--right:before {
right: 6px;
background-position: -94px -44px; }
/*
2. Share modal/popup and links
*/
.pswp__counter,
.pswp__share-modal {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none; }
.pswp__share-modal {
display: block;
background: rgba(0, 0, 0, 0.5);
width: 100%;
height: 100%;
top: 0;
left: 0;
padding: 10px;
position: absolute;
z-index: 1600;
opacity: 0;
-webkit-transition: opacity 0.25s ease-out;
transition: opacity 0.25s ease-out;
-webkit-backface-visibility: hidden;
will-change: opacity; }
.pswp__share-modal--hidden {
display: none; }
.pswp__share-tooltip {
z-index: 1620;
position: absolute;
background: #FFF;
top: 56px;
border-radius: 2px;
display: block;
width: auto;
right: 44px;
-webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.25);
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.25);
-webkit-transform: translateY(6px);
-ms-transform: translateY(6px);
transform: translateY(6px);
-webkit-transition: -webkit-transform 0.25s;
transition: transform 0.25s;
-webkit-backface-visibility: hidden;
will-change: transform; }
.pswp__share-tooltip a {
display: block;
padding: 8px 12px;
color: #000;
text-decoration: none;
font-size: 14px;
line-height: 18px; }
.pswp__share-tooltip a:hover {
text-decoration: none;
color: #000; }
.pswp__share-tooltip a:first-child {
/* round corners on the first/last list item */
border-radius: 2px 2px 0 0; }
.pswp__share-tooltip a:last-child {
border-radius: 0 0 2px 2px; }
.pswp__share-modal--fade-in {
opacity: 1; }
.pswp__share-modal--fade-in .pswp__share-tooltip {
-webkit-transform: translateY(0);
-ms-transform: translateY(0);
transform: translateY(0); }
/* increase size of share links on touch devices */
.pswp--touch .pswp__share-tooltip a {
padding: 16px 12px; }
a.pswp__share--facebook:before {
content: '';
display: block;
width: 0;
height: 0;
position: absolute;
top: -12px;
right: 15px;
border: 6px solid transparent;
border-bottom-color: #FFF;
-webkit-pointer-events: none;
-moz-pointer-events: none;
pointer-events: none; }
a.pswp__share--facebook:hover {
background: #3E5C9A;
color: #FFF; }
a.pswp__share--facebook:hover:before {
border-bottom-color: #3E5C9A; }
a.pswp__share--twitter:hover {
background: #55ACEE;
color: #FFF; }
a.pswp__share--pinterest:hover {
background: #CCC;
color: #CE272D; }
a.pswp__share--download:hover {
background: #DDD; }
/*
3. Index indicator ("1 of X" counter)
*/
.pswp__counter {
position: absolute;
left: 0;
top: 0;
height: 44px;
font-size: 13px;
line-height: 44px;
color: #FFF;
opacity: 0.75;
padding: 0 10px; }
/*
4. Caption
*/
.pswp__caption {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
min-height: 44px; }
.pswp__caption small {
font-size: 11px;
color: #BBB; }
.pswp__caption__center {
text-align: left;
max-width: 420px;
margin: 0 auto;
font-size: 13px;
padding: 10px;
line-height: 20px;
color: #CCC; }
.pswp__caption--empty {
display: none; }
/* Fake caption element, used to calculate height of next/prev image */
.pswp__caption--fake {
visibility: hidden; }
/*
5. Loading indicator (preloader)
You can play with it here - http://codepen.io/dimsemenov/pen/yyBWoR
*/
.pswp__preloader {
width: 44px;
height: 44px;
position: absolute;
top: 0;
left: 50%;
margin-left: -22px;
opacity: 0;
-webkit-transition: opacity 0.25s ease-out;
transition: opacity 0.25s ease-out;
will-change: opacity;
direction: ltr; }
.pswp__preloader__icn {
width: 20px;
height: 20px;
margin: 12px; }
.pswp__preloader--active {
opacity: 1; }
.pswp__preloader--active .pswp__preloader__icn {
/* We use .gif in browsers that don't support CSS animation */
background: url(preloader.gif) 0 0 no-repeat; }
.pswp--css_animation .pswp__preloader--active {
opacity: 1; }
.pswp--css_animation .pswp__preloader--active .pswp__preloader__icn {
-webkit-animation: clockwise 500ms linear infinite;
animation: clockwise 500ms linear infinite; }
.pswp--css_animation .pswp__preloader--active .pswp__preloader__donut {
-webkit-animation: donut-rotate 1000ms cubic-bezier(0.4, 0, 0.22, 1) infinite;
animation: donut-rotate 1000ms cubic-bezier(0.4, 0, 0.22, 1) infinite; }
.pswp--css_animation .pswp__preloader__icn {
background: none;
opacity: 0.75;
width: 14px;
height: 14px;
position: absolute;
left: 15px;
top: 15px;
margin: 0; }
.pswp--css_animation .pswp__preloader__cut {
/*
The idea of animating inner circle is based on Polymer ("material") loading indicator
by Keanu Lee https://blog.keanulee.com/2014/10/20/the-tale-of-three-spinners.html
*/
position: relative;
width: 7px;
height: 14px;
overflow: hidden; }
.pswp--css_animation .pswp__preloader__donut {
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 14px;
height: 14px;
border: 2px solid #FFF;
border-radius: 50%;
border-left-color: transparent;
border-bottom-color: transparent;
position: absolute;
top: 0;
left: 0;
background: none;
margin: 0; }
@media screen and (max-width: 1024px) {
.pswp__preloader {
position: relative;
left: auto;
top: auto;
margin: 0;
float: right; } }
@-webkit-keyframes clockwise {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg); }
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg); } }
@keyframes clockwise {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg); }
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg); } }
@-webkit-keyframes donut-rotate {
0% {
-webkit-transform: rotate(0);
transform: rotate(0); }
50% {
-webkit-transform: rotate(-140deg);
transform: rotate(-140deg); }
100% {
-webkit-transform: rotate(0);
transform: rotate(0); } }
@keyframes donut-rotate {
0% {
-webkit-transform: rotate(0);
transform: rotate(0); }
50% {
-webkit-transform: rotate(-140deg);
transform: rotate(-140deg); }
100% {
-webkit-transform: rotate(0);
transform: rotate(0); } }
/*
6. Additional styles
*/
/* root element of UI */
.pswp__ui {
-webkit-font-smoothing: auto;
visibility: visible;
opacity: 1;
z-index: 1550; }
/* top black bar with buttons and "1 of X" indicator */
.pswp__top-bar {
position: absolute;
left: 0;
top: 0;
height: 44px;
width: 100%; }
.pswp__caption,
.pswp__top-bar,
.pswp--has_mouse .pswp__button--arrow--left,
.pswp--has_mouse .pswp__button--arrow--right {
-webkit-backface-visibility: hidden;
will-change: opacity;
-webkit-transition: opacity 333ms cubic-bezier(0.4, 0, 0.22, 1);
transition: opacity 333ms cubic-bezier(0.4, 0, 0.22, 1); }
/* pswp--has_mouse class is added only when two subsequent mousemove events occur */
.pswp--has_mouse .pswp__button--arrow--left,
.pswp--has_mouse .pswp__button--arrow--right {
visibility: visible; }
.pswp__top-bar,
.pswp__caption {
background-color: rgba(0, 0, 0, 0.5); }
/* pswp__ui--fit class is added when main image "fits" between top bar and bottom bar (caption) */
.pswp__ui--fit .pswp__top-bar,
.pswp__ui--fit .pswp__caption {
background-color: rgba(0, 0, 0, 0.3); }
/* pswp__ui--idle class is added when mouse isn't moving for several seconds (JS option timeToIdle) */
.pswp__ui--idle .pswp__top-bar {
opacity: 0; }
.pswp__ui--idle .pswp__button--arrow--left,
.pswp__ui--idle .pswp__button--arrow--right {
opacity: 0; }
/*
pswp__ui--hidden class is added when controls are hidden
e.g. when user taps to toggle visibility of controls
*/
.pswp__ui--hidden .pswp__top-bar,
.pswp__ui--hidden .pswp__caption,
.pswp__ui--hidden .pswp__button--arrow--left,
.pswp__ui--hidden .pswp__button--arrow--right {
/* Force paint & create composition layer for controls. */
opacity: 0.001; }
/* pswp__ui--one-slide class is added when there is just one item in gallery */
.pswp__ui--one-slide .pswp__button--arrow--left,
.pswp__ui--one-slide .pswp__button--arrow--right,
.pswp__ui--one-slide .pswp__counter {
display: none; }
.pswp__element--disabled {
display: none !important; }
.pswp--minimal--dark .pswp__top-bar {
background: none; }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 547 B

View File

@ -1 +0,0 @@
<svg width="264" height="88" viewBox="0 0 264 88" xmlns="http://www.w3.org/2000/svg"><title>default-skin 2</title><g fill="none" fill-rule="evenodd"><g><path d="M67.002 59.5v3.768c-6.307.84-9.184 5.75-10.002 9.732 2.22-2.83 5.564-5.098 10.002-5.098V71.5L73 65.585 67.002 59.5z" id="Shape" fill="#fff"/><g fill="#fff"><path d="M13 29v-5h2v3h3v2h-5zM13 15h5v2h-3v3h-2v-5zM31 15v5h-2v-3h-3v-2h5zM31 29h-5v-2h3v-3h2v5z" id="Shape"/></g><g fill="#fff"><path d="M62 24v5h-2v-3h-3v-2h5zM62 20h-5v-2h3v-3h2v5zM70 20v-5h2v3h3v2h-5zM70 24h5v2h-3v3h-2v-5z"/></g><path d="M20.586 66l-5.656-5.656 1.414-1.414L22 64.586l5.656-5.656 1.414 1.414L23.414 66l5.656 5.656-1.414 1.414L22 67.414l-5.656 5.656-1.414-1.414L20.586 66z" fill="#fff"/><path d="M111.785 65.03L110 63.5l3-3.5h-10v-2h10l-3-3.5 1.785-1.468L117 59l-5.215 6.03z" fill="#fff"/><path d="M152.215 65.03L154 63.5l-3-3.5h10v-2h-10l3-3.5-1.785-1.468L147 59l5.215 6.03z" fill="#fff"/><g><path id="Rectangle-11" fill="#fff" d="M160.957 28.543l-3.25-3.25-1.413 1.414 3.25 3.25z"/><path d="M152.5 27c3.038 0 5.5-2.462 5.5-5.5s-2.462-5.5-5.5-5.5-5.5 2.462-5.5 5.5 2.462 5.5 5.5 5.5z" id="Oval-1" stroke="#fff" stroke-width="1.5"/><path fill="#fff" d="M150 21h5v1h-5z"/></g><g><path d="M116.957 28.543l-1.414 1.414-3.25-3.25 1.414-1.414 3.25 3.25z" fill="#fff"/><path d="M108.5 27c3.038 0 5.5-2.462 5.5-5.5s-2.462-5.5-5.5-5.5-5.5 2.462-5.5 5.5 2.462 5.5 5.5 5.5z" stroke="#fff" stroke-width="1.5"/><path fill="#fff" d="M106 21h5v1h-5z"/><path fill="#fff" d="M109.043 19.008l-.085 5-1-.017.085-5z"/></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 866 B

View File

@ -1,179 +0,0 @@
/*! PhotoSwipe main CSS by Dmitry Semenov | photoswipe.com | MIT license */
/*
Styles for basic PhotoSwipe functionality (sliding area, open/close transitions)
*/
/* pswp = photoswipe */
.pswp {
display: none;
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
overflow: hidden;
-ms-touch-action: none;
touch-action: none;
z-index: 1500;
-webkit-text-size-adjust: 100%;
/* create separate layer, to avoid paint on window.onscroll in webkit/blink */
-webkit-backface-visibility: hidden;
outline: none; }
.pswp * {
-webkit-box-sizing: border-box;
box-sizing: border-box; }
.pswp img {
max-width: none; }
/* style is added when JS option showHideOpacity is set to true */
.pswp--animate_opacity {
/* 0.001, because opacity:0 doesn't trigger Paint action, which causes lag at start of transition */
opacity: 0.001;
will-change: opacity;
/* for open/close transition */
-webkit-transition: opacity 333ms cubic-bezier(0.4, 0, 0.22, 1);
transition: opacity 333ms cubic-bezier(0.4, 0, 0.22, 1); }
.pswp--open {
display: block; }
.pswp--zoom-allowed .pswp__img {
/* autoprefixer: off */
cursor: -webkit-zoom-in;
cursor: -moz-zoom-in;
cursor: zoom-in; }
.pswp--zoomed-in .pswp__img {
/* autoprefixer: off */
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab; }
.pswp--dragging .pswp__img {
/* autoprefixer: off */
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: grabbing; }
/*
Background is added as a separate element.
As animating opacity is much faster than animating rgba() background-color.
*/
.pswp__bg {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #000;
opacity: 0;
-webkit-transform: translateZ(0);
transform: translateZ(0);
-webkit-backface-visibility: hidden;
will-change: opacity; }
.pswp__scroll-wrap {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: hidden; }
.pswp__container,
.pswp__zoom-wrap {
-ms-touch-action: none;
touch-action: none;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0; }
/* Prevent selection and tap highlights */
.pswp__container,
.pswp__img {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none; }
.pswp__zoom-wrap {
position: absolute;
width: 100%;
-webkit-transform-origin: left top;
-ms-transform-origin: left top;
transform-origin: left top;
/* for open/close transition */
-webkit-transition: -webkit-transform 333ms cubic-bezier(0.4, 0, 0.22, 1);
transition: transform 333ms cubic-bezier(0.4, 0, 0.22, 1); }
.pswp__bg {
will-change: opacity;
/* for open/close transition */
-webkit-transition: opacity 333ms cubic-bezier(0.4, 0, 0.22, 1);
transition: opacity 333ms cubic-bezier(0.4, 0, 0.22, 1); }
.pswp--animated-in .pswp__bg,
.pswp--animated-in .pswp__zoom-wrap {
-webkit-transition: none;
transition: none; }
.pswp__container,
.pswp__zoom-wrap {
-webkit-backface-visibility: hidden; }
.pswp__item {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
overflow: hidden; }
.pswp__img {
position: absolute;
width: auto;
height: auto;
top: 0;
left: 0; }
/*
stretched thumbnail or div placeholder element (see below)
style is added to avoid flickering in webkit/blink when layers overlap
*/
.pswp__img--placeholder {
-webkit-backface-visibility: hidden; }
/*
div element that matches size of large image
large image loads on top of it
*/
.pswp__img--placeholder--blank {
background: #222; }
.pswp--ie .pswp__img {
width: 100% !important;
height: auto !important;
left: 0;
top: 0; }
/*
Error message appears when image is not loaded
(JS option errorMsg controls markup)
*/
.pswp__error-msg {
position: absolute;
left: 0;
top: 50%;
width: 100%;
text-align: center;
font-size: 14px;
line-height: 16px;
margin-top: -8px;
color: #CCC; }
.pswp__error-msg a {
color: #CCC;
text-decoration: underline; }

9
public/css/viewer-js/viewer.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

10
public/js/viewer-js/viewer.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -9,13 +9,7 @@
"/js/blueimp-load-image/load-image.all.min.js": "/js/blueimp-load-image/load-image.all.min.js",
"/css/justified-gallery/justifiedGallery.min.css": "/css/justified-gallery/justifiedGallery.min.css",
"/js/justified-gallery/jquery.justifiedGallery.min.js": "/js/justified-gallery/jquery.justifiedGallery.min.js",
"/css/photo-swipe/photoswipe.css": "/css/photo-swipe/photoswipe.css",
"/css/photo-swipe/default-skin/default-skin.css": "/css/photo-swipe/default-skin/default-skin.css",
"/css/photo-swipe/default-skin/default-skin.png": "/css/photo-swipe/default-skin/default-skin.png",
"/css/photo-swipe/default-skin/default-skin.svg": "/css/photo-swipe/default-skin/default-skin.svg",
"/css/photo-swipe/default-skin/preloader.gif": "/css/photo-swipe/default-skin/preloader.gif",
"/js/photo-swipe/photoswipe.min.js": "/js/photo-swipe/photoswipe.min.js",
"/js/photo-swipe/photoswipe-ui-default.min.js": "/js/photo-swipe/photoswipe-ui-default.min.js",
"/js/photo-swipe/jquery.photoswipe-global.js": "/js/photo-swipe/jquery.photoswipe-global.js",
"/css/viewer-js/viewer.min.css": "/css/viewer-js/viewer.min.css",
"/js/viewer-js/viewer.min.js": "/js/viewer-js/viewer.min.js",
"/js/clipboard/clipboard.min.js": "/js/clipboard/clipboard.min.js"
}

View File

@ -30,3 +30,7 @@ progress::-webkit-progress-value {
.toast-close-button {
right: -0.15em;
}
.viewer-backdrop {
background-color: rgba(0, 0, 0, .75);
}

View File

@ -0,0 +1,5 @@
@props(['full' => request()->routeIs('images')])
<div {{ $attributes->merge(['class' => $full ? 'mx-auto sm:ml-64' : 'mx-auto sm:ml-64 px-6 md:px-10 lg:px-10 xl:px-10 2xl:px-60']) }}>
{{ $slot }}
</div>

View File

@ -12,7 +12,7 @@
x-transition:leave="transition ease-in duration-75"
x-transition:leave-start="transform opacity-100 scale-100"
x-transition:leave-end="transform opacity-0 scale-95"
class="absolute z-[1] {{ $classes[$direction] }} mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none"
class="absolute z-[9] {{ $classes[$direction] }} mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none"
role="menu"
aria-orientation="vertical"
aria-labelledby="user-menu-button"

View File

@ -1,4 +1,4 @@
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<svg {{ $attributes->merge(['class' => 'animate-spin -ml-1 mr-3 h-5 w-5 text-white']) }} xmlns="http://www.w3.org/2000/svg" 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>

Before

Width:  |  Height:  |  Size: 407 B

After

Width:  |  Height:  |  Size: 440 B

View File

@ -2,68 +2,145 @@
@push('styles')
<link rel="stylesheet" href="{{ asset('css/justified-gallery/justifiedGallery.min.css') }}">
<link rel="stylesheet" href="{{ asset('css/photo-swipe/photoswipe.css') }}">
<link rel="stylesheet" href="{{ asset('css/photo-swipe/default-skin/default-skin.css') }}">
<link rel="stylesheet" href="{{ asset('css/viewer-js/viewer.min.css') }}">
@endpush
<x-app-layout>
<div class="flex justify-between items-center bg-white h-12 border-solid border-b px-2">
<div class="hidden md:block">
<a class="text-sm py-2 px-3 hover:bg-gray-100 rounded text-gray-800" href=""><i class="fas fa-folder-open text-blue-500"></i> 新建相册</a>
</div>
<div class="block md:hidden">
<x-dropdown direction="right">
<x-slot name="trigger">
<a class="text-sm py-2 px-3 hover:bg-gray-100 rounded text-gray-800" href="javascript:void(0)">选中项 <i class="fas fa-chevron-down text-blue-500"></i></a>
</x-slot>
<div class="fixed z-[2] top-14 left-0 right-0 bg-white border-solid border-b">
<x-container class="px-2 flex justify-between items-center h-12">
<div class="hidden md:block">
<a class="text-sm py-2 px-3 hover:bg-gray-100 rounded text-gray-800" href=""><i class="fas fa-folder-open text-blue-500"></i> 新建相册</a>
</div>
<div class="block md:hidden">
<x-dropdown direction="right">
<x-slot name="trigger">
<a class="text-sm py-2 px-3 hover:bg-gray-100 rounded text-gray-800" href="javascript:void(0)">选中项 <i class="fas fa-chevron-down text-blue-500"></i></a>
</x-slot>
<x-slot name="content">
<x-dropdown-link href="{{ route('/') }}">移动到相册</x-dropdown-link>
<x-dropdown-link href="{{ route('/') }}">标记为不健康</x-dropdown-link>
<x-dropdown-link href="{{ route('/') }}" class="text-red-500">公开</x-dropdown-link>
<x-dropdown-link href="{{ route('/') }}" class="text-red-500">删除</x-dropdown-link>
</x-slot>
</x-dropdown>
</div>
<div class="flex space-x-2 items-center">
<input type="text" class="px-2.5 py-1.5 border-0 outline-none rounded bg-gray-100 text-sm transition-all duration-300 w-36 md:hover:w-52 md:focus:w-52" placeholder="输入关键字搜索...">
<x-dropdown direction="left">
<x-slot name="trigger">
<a class="text-sm py-2 px-3 hover:bg-gray-100 rounded text-gray-800" href="javascript:void(0)">排序 <i class="fas fa-sort-alpha-up text-blue-500"></i></a>
</x-slot>
<x-slot name="content">
<x-dropdown-link href="{{ route('/') }}">移动到相册</x-dropdown-link>
<x-dropdown-link href="{{ route('/') }}">标记为不健康</x-dropdown-link>
<x-dropdown-link href="{{ route('/') }}" class="text-red-500">公开</x-dropdown-link>
<x-dropdown-link href="{{ route('/') }}" class="text-red-500">删除</x-dropdown-link>
</x-slot>
</x-dropdown>
</div>
<div class="flex space-x-2 items-center">
<input type="text" class="px-2.5 py-1.5 border-0 outline-none rounded bg-gray-100 text-sm transition-all duration-300 w-36 md:hover:w-52 md:focus:w-52" placeholder="输入关键字搜索...">
<x-dropdown direction="left">
<x-slot name="trigger">
<a class="text-sm py-2 px-3 hover:bg-gray-100 rounded text-gray-800" href="javascript:void(0)">排序 <i class="fas fa-sort-alpha-up text-blue-500"></i></a>
</x-slot>
<x-slot name="content">
<x-dropdown-link href="javascript:void(0)">最新</x-dropdown-link>
<x-dropdown-link href="javascript:void(0)">最早</x-dropdown-link>
<x-dropdown-link href="javascript:void(0)">最大</x-dropdown-link>
<x-dropdown-link href="javascript:void(0)">最小</x-dropdown-link>
</x-slot>
</x-dropdown>
</div>
<x-slot name="content">
<x-dropdown-link href="javascript:void(0)" @click="setOrderBy('newest'); open = false">最新</x-dropdown-link>
<x-dropdown-link href="javascript:void(0)" @click="setOrderBy('earliest'); open = false">最早</x-dropdown-link>
<x-dropdown-link href="javascript:void(0)" @click="setOrderBy('utmost'); open = false">最大</x-dropdown-link>
<x-dropdown-link href="javascript:void(0)" @click="setOrderBy('least'); open = false">最小</x-dropdown-link>
</x-slot>
</x-dropdown>
</div>
</x-container>
</div>
<div id="photo-grid">
@foreach(Auth::user()->images()->get() as $image)
<a href="javascript:void(0)" class="rounded p-1 border-2 border-transparent hover:border-blue-500">
<img src="{{ $image->url }}">
</a>
@endforeach
<div id="photos-grid" class="overflow-hidden mt-6 md:mt-2"></div>
<div id="photos-loading" class="flex justify-center items-center pt-6 pb-6">
<a href="javascript:void(0)" class="flex justify-center items-center text-sm text-gray-400 text-gray-700 cursor-not-allowed">加载中...</a>
</div>
<script type="text/html" id="photos-item">
<a href="javascript:void(0)" class="rounded relative item">
<div class="absolute left-0 right-0 bottom-0 h-20 z-[1] bg-gradient-to-t from-black" onclick="$(this).siblings('img').trigger('click')">
<div class="absolute left-2 bottom-2 text-white z-[2] w-[90%]">
<p class="text-sm truncate" title="__name__">__name__</p>
<p class="text-xs" title="__human_date__">__date__</p>
</div>
</div>
<img alt="__name__" src="__url__">
</a>
</script>
@push('scripts')
<script src="{{ asset('js/justified-gallery/jquery.justifiedGallery.min.js') }}"></script>
<script src="{{ asset('js/photo-swipe/jquery.photoswipe-global.js') }}"></script>
<script src="{{ asset('js/viewer-js/viewer.min.js') }}"></script>
<script>
$("#photo-grid").justifiedGallery({
const $photos = $("#photos-grid");
const $loading = $("#photos-loading a");
const viewer = new Viewer(document.getElementById('photos-grid'), {});
let params = {
page: 1,
order: '',
};
let gridConfigs = {
rowHeight : 180,
margins: 10,
captions: false,
border: 10,
}).photoSwipe('img', {
allowPanToNext: true,
shareButtons: [
{id: 'facebook', label: 'Share on Facebook', url: 'https://www.facebook.com/sharer/sharer.php?u=@{{url}}'},
{id: 'twitter', label: 'Tweet', url: 'https://twitter.com/intent/tweet?text=@{{text}}&url=@{{url}}'},
{id: 'download', label: 'Download image', url: '@{{raw_image_url}}', download: true}
],
refreshSensitivity: (this.scrollbarWidth || 19) + 1, // 防止 viewer.js 打开时隐藏滚动条导致 grid 被重新计算
};
const getImages = () => {
if (params.page !== 1 && $loading.hasClass('cursor-not-allowed')) {
return;
}
$.ajax({
url: '',
type: 'post',
data: params,
beforeSend: function () {
$loading.text('加载中...').addClass('text-gray-400 cursor-not-allowed');
},
success: function (response) {
if (! response.status) {
return toastr.error(response.message);
}
let images = response.data.images.data;
if (images.length <= 0 || response.data.last_page === params.page) {
$loading.text('我也是有底线的~').addClass('text-gray-400 cursor-not-allowed');
return;
}
let html = '';
for (const i in images) {
html += $('#photos-item').html()
.replace(/__name__/g, images[i].filename)
.replace(/__human_date__/g, images[i].human_date)
.replace(/__date__/g, images[i].date)
.replace(/__url__/g, images[i].url)
}
$photos.append(html).justifiedGallery('norewind');
viewer.update();
params.page++;
setTimeout(function () {
$loading.text('加载更多').removeClass('text-gray-400 cursor-not-allowed');
}, 500)
},
});
};
const setOrderBy = function (sort) {
params.page = 1;
params.order = sort;
$photos.html('').justifiedGallery('destroy').justifiedGallery(gridConfigs)
getImages();
};
$('html').css('overflow-y', 'scroll')
$photos.justifiedGallery(gridConfigs);
$loading.click(() => getImages(params.page));
$(window).scroll(function() {
let scrollTop = $(this).scrollTop();
let scrollHeight = $(document).height();
let clientHeight = $(this).height();
if(scrollTop + clientHeight >= scrollHeight - 50) {
getImages();
}
});
setTimeout(() => getImages(), 500);
</script>
@endpush
</x-app-layout>

View File

@ -1,5 +1,4 @@
<!DOCTYPE html>
@props(['full' => request()->routeIs('images')])
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
@ -13,11 +12,11 @@
<!-- Fonts -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap">
<link rel="stylesheet" href="{{ asset('css/fontawesome.css') }}">
@stack('styles')
<!-- Styles -->
<link rel="stylesheet" href="{{ asset('css/common.css') }}">
<link rel="stylesheet" href="{{ asset('css/app.css') }}">
@stack('styles')
<!-- Scripts -->
<script src="{{ asset('js/app.js') }}"></script>
@ -25,7 +24,7 @@
<body class="font-sans antialiased">
<div class="min-h-screen bg-gray-100" x-data="{sidebarOpened: false}" x-cloak>
@include('layouts.sidebar')
@include('layouts.header', ['full' => $full])
@include('layouts.header')
<div
x-transition:enter="ease-in-out duration-500"
x-transition:enter-start="opacity-0"
@ -42,11 +41,9 @@
>
</div>
<main class="transition-all duration-300 sm:ml-64 pt-14">
<div class="w-full {{ $full ? '' : 'container mx-auto p-6 md:p-10 md:px-10 lg:px-10 xl:px-10 2xl:px-60' }}" style="min-height: calc(100vh - 3.5rem)">
{{ $slot }}
</div>
</main>
<x-container class="transition-all duration-300 pt-20 md:pt-24 pb-6">
{{ $slot }}
</x-container>
</div>
</body>
@stack('scripts')

View File

@ -12,11 +12,11 @@
<!-- Fonts -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap">
<link rel="stylesheet" href="{{ asset('css/fontawesome.css') }}">
@stack('styles')
<!-- Styles -->
<link rel="stylesheet" href="{{ asset('css/common.css') }}">
<link rel="stylesheet" href="{{ asset('css/app.css') }}">
@stack('styles')
<!-- Scripts -->
<script src="{{ asset('js/app.js') }}"></script>

View File

@ -1,7 +1,5 @@
@props(['full' => false])
<header class="pl-0 sm:pl-64 transition-all duration-300 w-full h-14 bg-gray-700 text-white flex justify-center fixed top-0 z-[9]">
<div class="{{ $full ? 'px-4' : 'container mx-auto px-6 md:px-10 md:px-10 lg:px-10 xl:px-10 2xl:px-60' }} w-full flex justify-between items-center">
<header class="transition-all duration-300 w-full h-14 bg-gray-700 text-white flex justify-center fixed top-0 z-[9]">
<x-container class="w-full px-6 flex justify-between items-center">
<div class="flex justify-start items-center max-w-[70%]">
<a href="javascript:void(0)" @click="sidebarOpened = ! sidebarOpened" class="w-6 h-6 p-4 rounded-full sm:hidden -ml-1 mr-4 flex justify-center items-center">
<i class="fas fa-bars text-xl"></i>
@ -11,5 +9,5 @@
<div class="flex justify-end items-center">
@include('layouts.user-nav')
</div>
</div>
</x-container>
</header>

View File

@ -21,7 +21,7 @@ Route::post('/upload', [Controller::class, 'upload']);
Route::group(['middleware' => ['auth']], function () {
Route::get('/dashboard', [UserController::class, 'dashboard'])->name('dashboard');
Route::get('/upload', fn () => view('upload'))->name('upload');
Route::get('/images', [ImageController::class, 'index'])->name('images');
Route::any('/images', [ImageController::class, 'index'])->name('images');
});
require __DIR__.'/auth.php';

10
webpack.mix.js vendored
View File

@ -28,13 +28,9 @@ mix.copy('node_modules/blueimp-load-image/js/load-image.all.min.js', 'public/js/
mix.copy('node_modules/justifiedGallery/dist/css/justifiedGallery.min.css', 'public/css/justified-gallery');
mix.copy('node_modules/justifiedGallery/dist/js/jquery.justifiedGallery.min.js', 'public/js/justified-gallery');
// photoSwipe
mix.copy('node_modules/photoswipe/dist/photoswipe.css', 'public/css/photo-swipe');
mix.copy('node_modules/photoswipe/dist/default-skin', 'public/css/photo-swipe/default-skin');
mix.copy('node_modules/photoswipe/dist/photoswipe.min.js', 'public/js/photo-swipe');
mix.copy('node_modules/photoswipe/dist/photoswipe-ui-default.min.js', 'public/js/photo-swipe')
// jquery Photoswipe
mix.copy('node_modules/jquery.photoswipe/dist/jquery.photoswipe-global.js', 'public/js/photo-swipe')
// viewer.js
mix.copy('node_modules/viewerjs/dist/viewer.min.css', 'public/css/viewer-js')
mix.copy('node_modules/viewerjs/dist/viewer.min.js', 'public/js/viewer-js')
// clipboard
mix.copy('node_modules/clipboard/dist/clipboard.min.js', 'public/js/clipboard')